Railway Operation Simulator  v2.23.2
A railway simulator for Windows
TrainUnit.cpp
Go to the documentation of this file.
1 // TrainUnit.cpp
2 /*
3  BEWARE OF COMMENTS in .cpp files: they were accurate when written but have
4  sometimes been overtaken by changes and not updated
5  Comments in .h files are believed to be accurate and up to date
6 
7  This is a source code file for "railway.exe", a railway operation
8  simulator, written originally in Borland C++ Builder 4 Professional with
9  later updates in Embarcadero C++Builder.
10  Copyright (C) 2010 Albert Ball [original development]
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25 // ---------------------------------------------------------------------------
26 #include <Classes.hpp>
27 #include <Controls.hpp>
28 #include <StdCtrls.hpp>
29 #include <Forms.hpp>
30 #include <Buttons.hpp>
31 #include <ExtCtrls.hpp>
32 #include <Menus.hpp>
33 #include <Dialogs.hpp>
34 #include <Graphics.hpp>
35 #include <ComCtrls.hpp>
36 #include <fstream>
37 #include <vector>
38 #include <algorithm> //for sort
39 #include <vcl.h>
40 #include <stdlib.h> //for rand() & random()
41 #include <math.hpp> //for speed & performance calcs
42 
43 #pragma hdrstop
44 
45 #include "TrainUnit.h"
46 #include "TrackUnit.h"
47 #include "TextUnit.h" //for displaying train service reference
48 #include "GraphicUnit.h"
49 //#include "DisplayUnit.h" included in TrackUnit.h
50 #include "PerfLogUnit.h"
51 #include "Utilities.h"
52 
53 // ---------------------------------------------------------------------------
54 #pragma package(smart_init)
55 
57 
58 // ---------------------------------------------------------------------------
59 
60 int TTrain::NextTrainID = 0; // has to be initialised outside the class
61 
62 // ---------------------------------------------------------------------------
63 
64 TExitInfo::TExitInfo() //default constructor
65 {
66  ServiceReference = " ";
67  RepeatNumber = 0;
68  TimeToExitSecs = -1;
69 }
70 
71 // ---------------------------------------------------------------------------
72 
73 TTrain::TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeedIn, int MassIn, double MaxRunningSpeedIn,
74  double MaxBrakeRateIn, double PowerAtRailIn, TTrainMode TrainModeIn, TTrainDataEntry *TrainDataEntryPtrIn, int RepeatNumberIn, int IncrementalMinutesIn,
75  int IncrementalDigitsIn, int SignallerMaxSpeedIn) : RearStartElement(RearStartElementIn), RearStartExitPos(RearStartExitPosIn), HeadCode(InputCode),
76  StartSpeed(StartSpeedIn), Mass(MassIn), MaxRunningSpeed(MaxRunningSpeedIn), MaxBrakeRate(MaxBrakeRateIn), PowerAtRail(PowerAtRailIn),
77  TrainMode(TrainModeIn), TrainDataEntryPtr(TrainDataEntryPtrIn), RepeatNumber(RepeatNumberIn), IncrementalMinutes(IncrementalMinutesIn),
78  IncrementalDigits(IncrementalDigitsIn), SignallerMaxSpeed(SignallerMaxSpeedIn)
79 /*
80  Construct a new train with general default values and input values for position and headcode.
81  Create the frontcode, headcode and background graphics here but don't delete them in a destructor.
82  This is because trains are kept in a vector and vectors erase elements during internal operations.
83  Deletion is explicit by using a special function. Increment the static class member NextTrainID
84  after setting this train's ID.
85 */
86 
87 {
88  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TTrain," + AnsiString(RearStartElementIn) + "," +
89  AnsiString(RearStartExitPosIn) + "," + AnsiString(InputCode) + "," + AnsiString(StartSpeedIn) + "," + AnsiString(MassIn) + "," +
90  AnsiString(TrainModeIn));
91  // AutoControl = true;//all trains start in auto control
92  UpdateCounter = 0;
93  TimeTimeLocArrived = false;
94  Derailed = false;
95  DerailPending = false;
96  Crashed = false;
97  StoppedAtBuffers = false;
98  StoppedAtSignal = false;
99  StoppedAtLocation = false;
100  StoppedAfterSPAD = false;
101  StoppedWithoutPower = false; // new at v2.4.0
102  StoppedForTrainInFront = false;
103  TrainInFront = false; //new at v2.18.0
104  SignallerStoppingFlag = false;
105  SignallerStopped = false;
106  SignallerRemoved = false;
107  NotInService = false;
108  HoldAtLocationInTTMode = false;
109  AllowedToPassRedSignal = false;
110  CallingOnFlag = false;
111  BeingCalledOn = false;
112  DepartureTimeSet = false;
114  TimetableFinished = false;
115  LastActionDelayFlag = false;
116  OneLengthAccelDecel = false;
117  TrainCrashedInto = -1;
119  Plotted = false;
120  TrainGone = false;
121  SPADFlag = false;
122  FrontCodePtr = new Graphics::TBitmap;
123  FrontCodePtr->PixelFormat = pf8bit;
124  FrontCodePtr->Height = 8;
125  FrontCodePtr->Width = 8;
127  FrontCodePtr->Transparent = false;
128  AValue = sqrt(2 * PowerAtRail / Mass);
130  TerminatedMessageSent = false;
131  JoinedOtherTrainFlag = false;
133  FollowOnServiceRef = ""; //added at v2.12.0
134  TreatPassAsTimeLocDeparture = false; //added at v2.12.0
135  StepForwardFlag = false;
137  for(int x = 0; x < 4; x++)
138  {
139  HeadCodeGrPtr[x] = new Graphics::TBitmap;
140  HeadCodeGrPtr[x]->PixelFormat = pf8bit;
141  HeadCodeGrPtr[x]->Height = 8;
142  HeadCodeGrPtr[x]->Width = 8;
144  HeadCodeGrPtr[x]->Transparent = false;
145  }
146  for(int x = 0; x < 4; x++)
147  {
148  BackgroundPtr[x] = new Graphics::TBitmap;
149  BackgroundPtr[x]->PixelFormat = pf8bit;
150  BackgroundPtr[x]->Height = 8;
151  BackgroundPtr[x]->Width = 8;
153  BackgroundPtr[x]->Transparent = false;
154  }
155  for(int x = 0; x < 4; x++)
156  {
158  // set here to ensure have values
159  }
160  for(int x = 0; x < 4; x++)
161  {
162  PlotElement[x] = -1; // marker for not plotted yet
163  }
164  for(int x = 0; x < 3; x++)
165  {
166  OldZoomOutElement[x] = -1; // marker for not plotted yet
167  }
169  NextTrainID++;
170 
171  // new values added to complete initialisation of all TTrain variables
172 
173  // ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)); can't be initialised yet as session trains created with Null
174  // TrainDataEntryPtr, initialise in AddTrain
176  FrontElementLength = 0;
177  EntrySpeed = 0;
178  ExitSpeedHalf = 0;
179  ExitSpeedFull = 0;
180  MaxExitSpeed = 0;
181  BrakeRate = 0;
182  CoastingBrakeRate = 0.03; //added at v2.18.0
184  FirstHalfMove = true;
185  EntryTime = 0;
186  ExitTimeHalf = 0;
187  ExitTimeFull = 0;
188  ReleaseTime = 0;
189  TRSTime = 0;
190  LastActionTime = 0;
191  Straddle = MidLag;
192  LeadElement = -1;
193  LeadEntryPos = 0;
194  LeadExitPos = 0;
195  MidElement = -1;
196  MidEntryPos = 0;
197  MidExitPos = 0;
198  LagElement = -1;
199  LagEntryPos = 0;
200  LagExitPos = 0;
201  TrainFailed = false; // added at v2.4.0
202  for(int x = 0; x < 4; x++)
203  {
204  HOffset[x] = 0;
205  VOffset[x] = 0;
206  PlotEntryPos[x] = 0;
207  }
208  OpTimeToAct = 60; // default value, new at v2.2.0
209  TimeToExit = -1;
210  ExitPair.first = -1;
211  ExitPair.second = -1;
212  MinsDelayed = 0.0; // new at v2.2.0
213  FirstLaterStopRecoverableTime = 0.0; // new at v2.2.0
214  FinishJoinLogSent = false;
215  // added at v2.4.0 to prevent repeatdly logging the event
218  // added at v2.4.0, no need to include in session file as will only be sent once & better that way
222  ZeroPowerNoCDTMessage = false;
227  ZeroPowerDepartMessage = false;
228  TrainInFrontMessage = false;
229  TrainFailurePending = false;
230  SkippedDeparture = false;
231  ActionsSkippedFlag = false;
232  SkipPtrValue = 0;
233  TrainSkippedEvents = 0;
234  DelayedRandMins = 0; //added at v2.13.0
235  NewDelay = 0; //added at v2.13.0
236  CumulativeDelayedRandMinsOneTrain = 0; //added at v2.13.0
237  ActualArrivalTime = TDateTime(0); //added at v2.13.0
238  LastSigPassedFailed = false; //added at v2.13.0
239  NonDefaultMinDwellTimeFlag = false; //added at v2.23.0
240  ArrivalMinDwellTime = 30.0; //added at v2.23.0, default value
241  LongServRefEnteredFlag = false;
242  LongServRefNameBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
243  LongServRefNameBitmap->PixelFormat = pf8bit;
244  LongServRefNameBitmap->Height = 13; //needs to be 13 so text can be entered, though top of text is 3 pixels below the top
245  LongServRefNameBitmap->Width = 54;
247 
248  LongServRefWorkingBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
249  LongServRefWorkingBitmap->PixelFormat = pf8bit;
250  LongServRefWorkingBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
251  LongServRefWorkingBitmap->Width = 54;
253 
254  ImageLongServRefBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs on the operating image
255  ImageLongServRefBitmap->PixelFormat = pf8bit;
256  ImageLongServRefBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
257  ImageLongServRefBitmap->Width = 54;
259  Utilities->CallLogPop(648);
260 }
261 
262 // ---------------------------------------------------------------------------
263 
264 void TTrain::DeleteTrain(int Caller)
265 /*
266  Delete train heap objects (bitmaps) explicitly by this special function rather than by a destructor, because vectors
267  erase elements during internal operations & if TTrain had an explicit destructor that deleted the heap elements then
268  it would be called when a vector element was erased. Calling the default TTrain destructor doesn't matter because all that
269  does is release the memory of the members (including pointers to the bitmaps), it doesn't destroy the bitmaps themselves.
270  It's important therefore to call this function before erasing the vector element, otherwise the pointers to the bitmaps
271  would be lost and the bitmaps never destroyed, thereby causing memory leaks.
272  No need to delete HeadCodePosition as that just points to existing bitmaps
273 */{
274  // if(NoDelete) return;//used when a TTrain is created to hold copied values from elsewhere
275  TrainController->LogEvent("" + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
276  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
277  if(Display->ZoomOutFlag)
278  {
280  }
281  if(FrontCodePtr == 0)
282  {
283  throw Exception("Error in attempting to delete FrontCodePtr");
284  }
285  delete FrontCodePtr;
286  FrontCodePtr = 0;
287  for(int x = 0; x < 4; x++)
288  {
289  if(BackgroundPtr[x] == 0)
290  {
291  throw Exception("Error in attempting to delete BackgroundPtr[" + AnsiString(x) + "]");
292  }
293  delete BackgroundPtr[x];
294  BackgroundPtr[x] = 0;
295  }
296  for(int x = 0; x < 4; x++)
297  {
298  if(HeadCodeGrPtr[x] == 0)
299  {
300  throw Exception("Error in attempting to delete HeadCodeGrPtr[" + AnsiString(x) + "]");
301  }
302  delete HeadCodeGrPtr[x];
303  HeadCodeGrPtr[x] = 0;
304  }
305  delete LongServRefNameBitmap;
307  delete ImageLongServRefBitmap;
311  Utilities->CallLogPop(649);
312 }
313 
314 // ---------------------------------------------------------------------------
315 
317 /*
318  Plots the train starting position on screen. Note that the check for starting on straight points &
319  on wrongly set points is carried out in TrainControllerUnit [but have to allow for starting on points because
320  ChangeDirection calls this function]. Train starts on Lead & Mid elements & Straddle = LeadMid unless
321  entering at a continuation in which case Straddle = MidLag & train not plotted immediately.
322  Set the headcode graphics pointers from the headcode text, then check whether starting at a
323  continuation. If so set Mid & Lag elements to -1 so they won't be plotted, and set Lead values
324  for the continuation element. Otherwise set Lead and Mid values,
325 
326  and Lead element value unless
327  Mid element is a buffer or continuation. Set Straddle, then for the Mid element set the graphic
328  offsets and headcode positions and front code. Pick up background bitmaps for the Mid element,
329  then check if a train on either Mid or Lag and if so give a warning message and return false so
330  that the calling function can delete the train. Plot the Mid element train values then do similarly
331  for the Lag element - set offsets, pick up background bitmaps, and plot the rear two segments of
332  the train. Finally set the Plotted flag and return true.
333 */{
334  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotStartPosition," + HeadCode);
335  int NextElementPosition, NextEntryPos, ElementLength, SpeedLimit;
336 
338  // PlotStartTime = TrainController->TTClockTime;
339  FirstHalfMove = true;
340 
341  // if enter at continuation then don't plot anything at start, but set TrainIDOnElement for continuation entry so as to
342  // 'claim' it for this train to prevent any other waiting trains trying to enter
344  {
345  LagElement = -1; // not to be plotted
346  LagExitPos = 0; // not to be plotted
347  LagEntryPos = 0; // not to be plotted
348  MidElement = -1; // not to be plotted
349  MidExitPos = 0; // not to be plotted
350  MidEntryPos = 0; // not to be plotted
352  LeadExitPos = 1; // will be 1 for continuation entry
353  LeadEntryPos = 0;
354 
356  MaxExitSpeed = StartSpeed; // initial value
358  ElementLength = Track->TrackElementAt(164, LeadElement).Length01;
359  SpeedLimit = Track->TrackElementAt(165, LeadElement).SpeedLimit01;
360  if(EntrySpeed > SpeedLimit)
361  {
362  EntrySpeed = SpeedLimit;
363  }
365  {
367  }
369  // LeadElement is the element to be entered
370 
371  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
372  // can achieve ExitSpeedFull at the half braking rate.
374  {
375  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength)); // half braking
376  if(TempEntrySpeed < EntrySpeed)
377  {
378  EntrySpeed = TempEntrySpeed;
380  }
381  }
382  Straddle = MidLag; // only for starting on a continuation
384  // no need to stop gap flashing if start on continuation
385  }
386  else // not starting at a continuation
387  {
388  LagElement = -1;
389  LagEntryPos = 0;
390  LagExitPos = 0;
397 
399  MaxExitSpeed = StartSpeed; // initial value
401  bool TempDerail = false; // dummy
402  NextElementPosition = Track->TrackElementAt(168, LeadElement).Conn[Track->GetAnyElementOppositeLinkPos(2, LeadElement, LeadEntryPos, TempDerail)];
404  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
405  {
406  StoppedWithoutPower = true;
407  }
408  // facing buffers check - ignore starting speed if start facing buffers
409  StoppedAtBuffers = false;
410  // need to set here as well as in UpdateTrain() in case paused during signaller change direction
413  {
414  FrontElementSpeedLimit = Track->TrackElementAt(494, LeadElement).SpeedLimit01; // use 01 for convenience, not used
415  FrontElementLength = Track->TrackElementAt(495, LeadElement).Length01; // use 01 for convenience, not used
416  EntrySpeed = 0;
417  ExitSpeedHalf = 0;
418  ExitSpeedFull = 0;
419  MaxExitSpeed = 0;
420  // SetTrainMovementValues not called so set this here
421  BrakeRate = 0;
424  StoppedAtSignal = false;
425  // new v2.2.0: can't be at buffers and signal! If was set then won't be reset as later
426  // signal check is an 'else'
427  if(!StoppedAtLocation)
428  {
429  StoppedAtBuffers = true; // stopped at location takes precedence
430  }
431  }
432 
433  // facing continuation check - don't allow to stop even if no power
435  {
436  FrontElementSpeedLimit = Track->TrackElementAt(509, LeadElement).SpeedLimit01; // use 01 for convenience, not used
437  FrontElementLength = Track->TrackElementAt(510, LeadElement).Length01; // use 01 for convenience, not used
441  BrakeRate = 0;
442  ExitTimeHalf = TrainController->TTClockTime + TDateTime(1.8 * (double) FrontElementLength / EntrySpeed / 86400);
443  ExitTimeFull = TrainController->TTClockTime + TDateTime(3.6 * (double) FrontElementLength / EntrySpeed / 86400);
444  }
445 
446  // Signal check
447  else if((NextElementPosition > -1) && (NextEntryPos > -1))
448  // condition check added as precaution after SloughIECC error reported by James U
449  {
450  if((Track->TrackElementAt(170, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
451  (Track->TrackElementAt(171, NextElementPosition).Attribute == 0) && !StoppedWithoutPower)
452  {
453  FrontElementSpeedLimit = Track->TrackElementAt(172, LeadElement).SpeedLimit01; // use 01 for convenience, not used
454  FrontElementLength = Track->TrackElementAt(173, LeadElement).Length01; // use 01 for convenience, not used
455  EntrySpeed = 0;
456  ExitSpeedHalf = 0;
457  ExitSpeedFull = 0;
458  MaxExitSpeed = 0;
459  BrakeRate = 0;
462  if(!StoppedAtLocation) //if it is stopped at location then don't want StoppedAtSignal until departure time if still red then, & UpdateTrain takes care of thet
463  {
464  StoppedAtSignal = true;
466  // TrainController->LogActionError(39, HeadCode, "", SignalHold, Track->TrackElementAt(754, NextElementPosition).ElementID);
467  }
469  {
470  // set both StoppedAtLocation & StoppedAtSignal, so that 'pass stop signal' is offered in popup menu rather than move
471  // forwards, but don't change the background colour so still shows as stopped at location
472  StoppedAtSignal = true;
473  }
474  }
475  else
476  {
477  StoppedAtSignal = false;
478  if(NextEntryPos > 1)
479  {
480  ElementLength = Track->TrackElementAt(174, NextElementPosition).Length23;
481  SpeedLimit = Track->TrackElementAt(175, NextElementPosition).SpeedLimit23;
482  }
483  else
484  {
485  ElementLength = Track->TrackElementAt(176, NextElementPosition).Length01;
486  SpeedLimit = Track->TrackElementAt(177, NextElementPosition).SpeedLimit01;
487  }
488  if(EntrySpeed > SpeedLimit)
489  {
490  EntrySpeed = SpeedLimit;
491  }
493  {
495  }
497  TDateTime TestTime = TrainController->TTClockTime; // test
498  AnsiString TimeString = Utilities->Format96HHMMSS(TestTime); // test
499  SetTrainMovementValues(2, NextElementPosition, NextEntryPos);
500  // NextElement is the element to be entered
501 
502  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
503  // can achieve ExitSpeedFull at the half braking rate.
505  {
506  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength));
507  // half braking
508  if(TempEntrySpeed < EntrySpeed)
509  {
510  EntrySpeed = TempEntrySpeed;
511  SetTrainMovementValues(3, NextElementPosition, NextEntryPos);
512  }
513  }
514  }
515  }
517  {
518  throw Exception("Error, LeadElement Exit Connection is NotSet");
519  }
520  }
521  if(MidElement > -1) // will be -1 if start on continuation
522  {
523  Straddle = LeadMid;
527  {
528  for(int x = 0; x < 4; x++)
529  {
530  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
531  }
532  }
533  else
534  {
535  for(int x = 0; x < 4; x++)
536  {
538  }
539  }
540  if(TrainMode == Timetable)
541  {
543  }
544  else
545  {
547  }
549  // pick up background bitmaps [0] & [1] & plot HeadCodes [0] & [1]
550 
553 /* Move check to AddTrain, also, now that can start on bridges need to check that other train is on same track before refusing
554  if((Track->TrackElementAt(182, LeadElement).TrainIDOnElement > -1) || ((MidElement > -1) && (Track->TrackElementAt(183, MidElement).TrainIDOnElement > -1)))
555  {
556  ShowMessage("Can't place train " + HeadCode + "; another train already present at location");
557  Utilities->CallLogPop(651);
558  return false;
559  }
560 */
565  PlotTrainGraphic(8, 0, Display);
566  PlotTrainGraphic(9, 1, Display);
567 
570 
571  // pick up background bitmaps [2] & [3]
572 
575 
576  PlotElement[2] = MidElement;
578  PlotElement[3] = MidElement;
580  PlotTrainGraphic(10, 2, Display);
581  PlotTrainGraphic(11, 3, Display);
582  // Plotted = true; set in PlotTrainGraphic
583  //below handles long serv ref display
585  }
586  Display->Update(); // resurrected when Update() dropped from PlotOutput etc
587  Utilities->CallLogPop(652);
588 }
589 
590 // ---------------------------------------------------------------------------
591 void TTrain::UnplotTrain(int Caller)
592 {
593  // Note: If trouble is experienced with the PlotAlternativeTrackRouteGraphic functions remove them & test for train on a bridge and if so call Clearand..
594  if(!Plotted)
595  {
596  return;
597  }
598  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrain," + HeadCode);
599 
600  if(Straddle == MidLag)
601  {
602  if(MidElement > -1)
603  {
608  // to force plot of locked route marker, needed once only for the element
609  }
610  if(LagElement > -1)
611  {
616  // to force plot of locked route marker, needed once only for the element
617  }
618  }
619  else if(Straddle == LeadMidLag)
620  {
621  if(LeadElement > -1)
622  {
625  // to force plot of locked route marker, needed once only for the element
626  }
627  if(MidElement > -1)
628  {
633  // to force plot of locked route marker, needed once only for the element
634  }
635  if(LagElement > -1)
636  {
639  // to force plot of locked route marker, needed once only for the element
640  }
641  }
642  else if(Straddle == LeadMid)
643  {
644  if(LeadElement > -1)
645  {
650  // to force plot of locked route marker, needed once only for the element
651  }
652  if(MidElement > -1)
653  {
658  // to force plot of locked route marker, needed once only for the element
659  }
660  }
661  if(LeadElement > -1)
662  {
664  }
665  if(MidElement > -1)
666  {
668  }
669  if(LagElement > -1)
670  {
672  }
673  Plotted = false;
676  {
678  }
679  Display->Update();
680  // without this the screen 'blinks' at next Clearand... prob forces a full repaint for some reason
681  // resurrected when Update() dropped from PlotOutput etc
682  Utilities->CallLogPop(653);
683 }
684 
685 // ----------------------------------------------------------------------------
686 
687 void TTrain::UpdateTrain(int Caller)
688 /*
689  Note: Some changes made since comments written
690 
691  Brief:
692  Enter with Straddle defining train position wrt Lag, Mid & Lead elements. Is only MidLag at this point
693  on first entry at a continuation (with no train plotted), in all other cases it is either LeadMid (when train fully
694  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
695  Thereafter on entry Straddle = LeadMidLag or LeadMid; LeadMid if train fully on Mid & Lead elements, and
696  LeadMidLag if on Lag, Mid and Lead elements (back on lag, front on Lead, & middle 2 segments on Mid).
697  If enter with Straddle = LeadMid, then train is in effect in the first half of the next element, and moves half onto it after
698  the half time point has been passed. The values for the next element were set when the train was last updated when Straddle became
699  LeadMid from LeadMidLag. After the half time point has been passed Straddle is
700  changed to MidLag within the function and all elements moved down one, old Mid becomes
701  the new lag, old Lead becomes the new Mid, and a new Lead is obtained. Then the new positions are plotted, and finally Straddle is
702  incremented to reflect the position the train now occupies.
703 
704  Detail:
705  Set TrainFailurePending if all conditions met
706  Check whether stopped at a non-red signal, and if so reset StoppedAtSignal so train can move.
707  Check whether buffers at immediate exit, either when first enter the function or later, and set StoppedAtBuffers if so
708  and return.
709  If Straddle == LeadMid then train fully on Lead and Mid, so ready for a major update:-
710  If there's a LagElement (there will be but include check for good practice - next
711  function depends on it) Check whether DerailPending set - set during last GetLeadElement if appropriate but only acted on here when
712  train fully on offending point - Derail set and DerailPanding reset, train background
713  colour changed (note that BackgroundColour is a property of the train itself) then return.
714  If no derail pending reset Lag and Mid elements to the old Mid and Lead values, reset Straddle to MidLag, then set
715  the new LeadElement, which will be the next connected element (obtain using GetLeadElement) or -1 if the current
716  LeadElement is an exit continuation. During GetLeadElement the element at LeadElement is checked and if a stop
717  signal is found StoppedAtSignal is set to true, otherwise StoppedAtSignal is set to false. Also Derail is set
718  if LeadElement is a fouled trailing point.
719  Now, the train is moved on by one segment. Firstly the last BackgroundElement is set to LagElement, then the last
720  segment of the LagElement is unplotted (if there is a LagElement - may be entering at a continuation), by
721  replotting the last background segment and checking whether the element is a bridge or crossover with the other
722  track in a route, in which case the route colour is replotted.
723  Then, if Straddle == LeadMidLag (train will move completely off the element during this function), and the train
724  track is in a route, then all the train elements are removed from the route unless it's an autosig route. Normally only the
725  LeadElement will be in a route for a moving train, but when originally placed all elements may be in the route so check them all.
726  Note also that there may be two routes at a given element position, but only one of them is the correct one, so this
727  is identified prior to the removal. Also the TrainIDs are reset because the train will be fully off this element at the end of
728  the function. If Straddle == LeadMidlag and the element being left is a ContinuationExit the the TrainGone flag is set so the
729  train can be deleted by the calling function, and the function returns.
730  If the element is a signal in the train movement direction, then it is reset to red (Attribute = 0) and is replotted
731  to show the red aspect. Finally if element is a signal in the other direction it is replotted as it was - need to
732  plot individually because could have any aspect, the background bitmap that was picked up earlier contains just the
733  basic red aspect.
734 
735  Now all the array values are updated, but the [0] values are as yet invalid, these have to be obtained explicitly from
736  the new LeadElement later. The headcode graphics are updated so that it reads correctly - left to right & top to bottom,
737  regardless of direction, and with the correct front code colour.
738 
739  The new front segment background bitmap is now picked up and the graphic offsets set, and the segments are plotted.
740  No more unplotting is needed as all but the last segment are overwritten by later segments, and the new front
741  segment is just plotted, though the background bitmap at that location has to be picked up. Just where they are
742  plotted depends on the Straddle value, [0] is always on Lead, [1] is on Lead if Straddle == LeadMidLag or Mid if
743  Straddle == MidLag; [2] is always on Mid, and [3] is on Mid if Straddle == LeadMidLag or Lag if Straddle == MidLag.
744  Also prior to plotting the lead segment a crash check is made, and if true the Crashed flag is set and the
745  TrainCrashedInto value also set to the current TrainID - this is so it too becomes crashed and hence stopped.
746 
747  The Crashed flag is now checked, and if set the front headcode colour is changed to the same as the rest of the code,
748  and the background colour changed. Then the train that is crashed into is also set to Crashed, and its colours
749  changed similarly. The function then returns.
750 
751  If Crashed is not set then Straddle is incremented and the function returns.
752 */
753 
754 {
755  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UpdateTrain," + HeadCode);
756  UpdateCounter++;
757  // 100 counts = 5secs (used in splits to prevent too frequent length checks in front & rear splits)
758  if(UpdateCounter >= 100)
759  {
760  UpdateCounter = 0;
761  }
762  int RandRange = (TrainController->MTBFHours * 3600) / 53;
763 
764  // MTBFHours is in timetable clock hours, min value is 1 & max value is 9,999 (integer values on input)
765  // but double on use because it represents timetable clock time, so at 1/16 speed RandRange is * 16 (160,000 max) & at 16x speed its /16 (1/16 min)
766  // i.e MTBFHours is Input value/TTClockSpeed (conversion is done in InterfaceUnit)
767  if(int(TrainController->RandomFailureCounter) == (rand() % 1060))
768  // RandomFailureCounter value is fixed for a full cycle of train updates so this
769  // makes sure there's no bunching of failures as there is for a fixed comparison number
770  // or a small range of comparison numbers. True every 53 secs (real time) on average rand()
771  // gives a random number between 0 and 16384 (defined as RAND_MAX in stdlib.h)
772  {
773  if(!TrainFailed && !TrainOnContinuation(0) && (RandRange > 0) && (PowerAtRail > 1) && !((TrainMode == Timetable) && TimetableFinished)
774  && !Crashed && !Derailed && !((TrainMode == Signaller) && Stopped()))
775  // RandomFailureCounter resets to 0 every 53 secs, if RandRange is 0 then no failure rate is set - i.e. failure rate = 0
776  // don't fail if:
777  // (a) on a continuation (entering or leaving);
778  // (b) already failed;
779  // (c) power effectively zero (8000) min value for powered; 0.08 for 'no power';
780  // (d) train terminated;
781  // (e) crashed or derailed; or
782  // (f) under signaller control and stopped.
783  // (g) TreatPassAsTimeLocDeparture is true //added at v2.12.0
784  {
785  if((random(RandRange) == 0) && !TreatPassAsTimeLocDeparture)
786  // max value for RandRange is over 2x10^9
787  {
788  // here if failure due
789  TrainFailurePending = true;
790  // the failure occurs when PlotElements set to proper Lead & Mid Elements
791  }
792  }
793  }
794 /* dropped as it allows a train to stop on a half element - when reach (if Stopped()) at line 1310
795  if ((PowerAtRail < 1) && (EntrySpeed < 1)) // added at v2.4.0
796  {
797  StoppedWithoutPower = true;
798  }
799 */
800 // float TimeToExit; //added at v2.10.0 Removed these so original values retained - used when train on continuation
801 // THVShortPair ExitPair; //added at v2.10.0
802  int LockedVectorNumber;
803  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
804  // default values - these needed for route checker below
805  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
806 
808  {
810  }
811  if(Crashed || Derailed)
812  {
814  {
815  PlotTrain(7, Display);
816  // replotted every cycle because of level crossing crashes, otherwise a flashing level crossing wipes out half of the train
817  Display->Update();
818  }
819  OpTimeToAct = 0.0;
820  // need to set this here as wouldn't be calculated otherwise as return from UpdateTrain
821  Utilities->CallLogPop(1017);
822  return; // no further action, user has to remove or work around
823  }
824 
826  {
828  }
830  {
832  }
833  if(StoppedWithoutPower && (TrainMode == Signaller)) //added at v2.13.2 as this condition not covered - was shown as normal
834  {
836  }
838  // introduced at v1.2.0, formerly 'TimeTimeLocArrived = false' was included
839  // in the next condition 'if(!Stopped() && !SPADFlag)' which led to repeated arrival messages if signaller control allowed a train
840  // to move & then stop again at the same station
841  {
842  TimeTimeLocArrived = false;
843  }
844  if(!Stopped() && !SPADFlag && !TrainFailed)
845  {
847  }
848  // set or release StoppedAtBuffers if fully on 2 elements depending on LeadElement
849  // Note that if LeadElement == Buffers train must be facing the buffer so no need to check orientation
850 /* old version where force a stop at buffers regardless of speed
851  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(, LeadElement).TrackType == Buffers)) StoppedAtBuffers = true;
852  else StoppedAtBuffers = false;
853 */
854 
855  // new version where crash if run into buffers
856  if(!Crashed)
857  {
858  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(602, LeadElement).TrackType == Buffers))
859  {
860  if(ExitSpeedFull > 1)
861  {
862  Crashed = true;
866  // SendMissedActionLogs(3, -1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
867  // no need for missed action logs - will be sent when train removed
868  StoppedAtBuffers = false;
869  }
871  // stopped at location & stopped without power take precedence
872  {
873  StoppedAtBuffers = true;
874  }
875  else
876  {
877  StoppedAtBuffers = false;
878  }
879  }
880  else
881  {
882  StoppedAtBuffers = false;
883  }
884  }
885  else
886  {
887  StoppedAtBuffers = false;
888  }
889  // if crashed don't want stopped at buffers set
890 
891  // also crash if run into a level crossing that is changing or has barriers up
892  if(!Crashed)
893  {
894  if((Straddle == LeadMid) && (LeadElement > -1) && (ExitSpeedFull > 1))
895  {
896  int H = Track->TrackElementAt(873, LeadElement).HLoc;
897  int V = Track->TrackElementAt(874, LeadElement).VLoc;
898  if(Track->IsLCAtHV(40, H, V) && !Track->IsLCBarrierDownAtHV(2, H, V))
899  {
900  Crashed = true;
904  // no need for missed action logs - will be sent when train removed
905  }
906  }
907  }
909  {
911  }
912  // set or reset HoldAtLocationInTTMode (if true then actions are needed before train departs)
914  //if Command == "" then either TimeLoc or TimeTimeLoc so don't hold, and added last part at v2.12.0 so don't hold if have both command == pas and Treat... flag
915  {
916  HoldAtLocationInTTMode = true;
917  }
918  else if(TrainMode == Timetable)
919  {
920  HoldAtLocationInTTMode = false;
921  }
922  // in Signaller mode HoldAtLocationInTTMode not changed
923  // check if departure pending & set times unless already set
924  if(TrainMode == Timetable)
925  {
927  // && !StoppedAtBuffers) - drop this, set times whether or not at buffers
928  {
929  if((ActionVectorEntryPtr->Command != "pas") && (ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) && (ActualArrivalTime > TDateTime(0)))
930  {
931  AnsiString ReasonArray[24] = {"a driver is awaited","a guard is awaited","of a medical emergency","of a technical problem","of a security issue",
932  "of a safety issue","of a disturbance","a train crew member has been taken ill","the driver has been taken ill","the guard has been taken ill",
933  "a report has been received concerning safety","a shoe has been lost under the train","of a reported theft",
934  "of an incident involving an animal","some luggage has been lost under the train","a minor repair is needed","a suspicious object has to be dealt with safely",
935  "a door is stuck open","additional stock has to be attached","a security alert","of a train fault","of an operating incident","safety checks are required",
936  "of a shortage of on train crew"};
937  //(ActionVectorEntryPtr->Command != "pas") added at v2.13.0 to rule out passes, though probably not needed
938  //(ActualArrivalTime > TDateTime(0)) added at v2.13.0 to ensure that it has been set and to dismiss trains that are present
939  //at start or have no departure time set.
940  NewDelay = 0; //section relating to random delays added at v2.13.0
941  TDateTime TimetableReleaseTime = TrainController->GetRepeatTime(0, ActionVectorEntryPtr->DepartureTime, RepeatNumber, IncrementalMinutes); //Timetable value
942  TDateTime DwellTime = TimetableReleaseTime - ActualArrivalTime; //Timetable value
943 //diagnostic additions
944 //double aa = double(ActionVectorEntryPtr->DepartureTime) * 24;
945 //double bb = double(ActualArrivalTime) * 24;
946 //double cc = double(TimetableReleaseTime) * 24;
947 //double dd = double(ArrivalMinDwellTime); //secs
948  if(DwellTime < TDateTime(ArrivalMinDwellTime / 86400))
949  {
950  DwellTime = TDateTime(ArrivalMinDwellTime / 86400);
951  }
952  int randval = random(10000);
953  if(randval != 0) //if randval == 0 or DelayMode == Nil then NewDelay will be 0 as set above
954  {
955  if(Utilities->DelayMode == Minor)
956  {
957  if(randval < Utilities->MinorDelayCutoff)
958  {
959  NewDelay = Utilities->MinorDelayFactor * log(Utilities->MinorDelayCutoff/randval);//minutes (confusingly log in C++Builder gives the natural logarithm)
960  }
961  }
962  else if(Utilities->DelayMode == Moderate)
963  {
964  if(randval < Utilities->ModerateDelayCutoff)
965  {
967  }
968  }
969  else if(Utilities->DelayMode == Major)
970  {
971  if(randval < Utilities->MajorDelayCutoff)
972  {
974  }
975  }
976  }
977 //NewDelay = 5; //test
978  if(double(TrainController->TTClockTime) <= (Utilities->LastDelayTTClockTime + 5.0/1440.0)) //if within 5 mins of last delay for any train
979  { //then don't delay. Added at v2.13.0
980  NewDelay = 0;
981  }
982  if(NewDelay < 1)
983  {
984  NewDelay = 0;
985  }
986  if(NewDelay < double(DwellTime) * 1440) //if less than scheduled dwell time then no additional delay
987  {
988  NewDelay = 0;
989  }
990  else
991  {
992  NewDelay -= double(DwellTime) * 1440;//reduce delay by dwell time
993  }
994  if(DelayedRandMins > 0)
995  {
996  DelayedRandMins -= double(DwellTime) * 1440;//reduce knock-on random delay by dwell time
997  }
998  if(DelayedRandMins < 0)
999  {
1000  DelayedRandMins = 0;//can't be less than zero
1001  }
1003  {
1004  NewDelay -= DelayedRandMins; //NewDelay is the additional delay over and above the existing knock-on delay (from earlier random delays)
1005  //the formula above already includes knock-on effects
1006  DelayedRandMins += NewDelay; //the new total delay, knock-on + additional
1007 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //don't add here, add when depart, else this value can be > late mins
1008  }
1009  else
1010  {
1011  NewDelay = 0;
1012 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //as above
1013  }
1014  ReleaseTime = LastActionTime + TDateTime(NewDelay / 1440); //earliest possible release time
1015 // if(NewDelay < (ArrivalMinDwellTime / 60)) //less than the min dwell time
1016  if(ReleaseTime < LastActionTime + TDateTime(30.0 / 86400)) //30sec delay between actions
1017  {
1018  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1019  }
1020  if(ReleaseTime < ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400)) //lowest value of ArrivalMinDwellTime is 30s
1021  {
1022  ReleaseTime = ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400);
1023  }
1024  if(ReleaseTime < TimetableReleaseTime)
1025  {
1026  ReleaseTime = TimetableReleaseTime; //back to correct time
1027  NewDelay = 0;
1028  DelayedRandMins = 0;
1029  }
1030  if(DelayedRandMins > double(ReleaseTime - TimetableReleaseTime) * 1440)
1031  {
1032  DelayedRandMins = double(ReleaseTime - TimetableReleaseTime) * 1440; //reduce this if time has been made up
1033  }
1034 
1035  if(DelayedRandMins < NewDelay) //may be if reduced above, but if so need to reduce NewDelay also
1036  {
1038  }
1039  //may be possible to simplify all the above but as it seems to work ok leave as is
1040  if(int(NewDelay) > 0) //additional delay over and above knock-on effects from earlier random delays
1041  {
1043  if(int(NewDelay) == 1)
1044  {
1045  Display->WarningLog(12, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1046  ActionVectorEntryPtr->LocationName + " by 1 minute");
1047  PerfLogForm->PerformanceLog(18, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: " + HeadCode + " delayed at " +
1048  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1049  TrainController->StopTTClockMessage(140, HeadCode + " delayed at " +
1050  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1051  }
1052  else
1053  {
1054  Display->WarningLog(11, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1055  ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) + " minutes");
1056  if(NewDelay >= 10) //give variable reasons for >= 10 mins
1057  {
1058  int randval2 = rand() % 24; //24 reasons
1059  AnsiString Reason = ReasonArray[randval2];
1061  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1062  " minutes because " + Reason);
1063  TrainController->StopTTClockMessage(141, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1064  " minutes because " + Reason);
1065  }
1066  else
1067  {
1069  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1070  " minutes because of a minor problem");
1071  TrainController->StopTTClockMessage(142, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1072  " minutes because of a minor problem");
1073  }
1074  }
1075  }
1076  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1077  DepartureTimeSet = true; //reset ActualArrivalTime, ArrivalMinDwellTime and NonDefaultMinDwellTimeFlag when depart.
1078  }
1079  else if(ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) //as was, for trains that don't have an arrival time set
1080  {//if have skipped to a new service then DepartureTime will be set (in above segment when earlier train arrived)
1081  //but ArrivalTime won't be set as it is reset to 0 at end of above segement when earlier train arrived, so this segement
1082  //will run without any random delays which might cause timing errors from mixing modifications but at least shouldn't crash.
1083  NewDelay = 0;
1084  DelayedRandMins = 0;
1086  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1087  {
1088  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1089  }
1090  if(ReleaseTime <= ActualArrivalTime + TDateTime(ActionVectorEntryPtr->MinDwellTime / 86400))
1091  {
1093  }
1094  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1095  DepartureTimeSet = true;
1096  }
1097  else if((ActionVectorEntryPtr->Command == "pas") && TreatPassAsTimeLocDeparture) //new segment at v2.12.0 to treat a pass as a departure
1098  {//for when skip to a new service at a pass location. As above this also avoids any random delays, and will avoid above segment because
1099  //departure time isn't set - it's an event time. Again random delays in this situation might cause timing errors
1100  //from mixing modifications. Note that can't set a min dwell time for pas commands
1101  NewDelay = 0;
1102  DelayedRandMins = 0;
1104  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1105  {
1106  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1107  }
1108  if(ReleaseTime <= ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400))
1109  {
1110  ReleaseTime = ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400);
1111  }
1112  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1113  DepartureTimeSet = true;
1114  }
1115  }
1116  }
1117  if(TrainController->OpTimeToActUpdateCounter == 0)// && TrainController->OpActionPanelVisible) removed last condition so always calc TimeToExit
1118  {
1119  OpTimeToAct = CalcTimeToAct(0, TimeToExit, ExitPair); // called after ReleaseTime set
1120 // this->TimeToExit = TimeToExit; don't need these as values updated directly
1121 // this->ExitPair = ExitPair;
1122  // calculate every 1 sec (in real time, not timetable time) for all trains
1123  }
1124  // check if being held at location pending any actions & deal with them if time appropriate & >= 30s since LastActionTime
1125  if(TrainMode == Timetable)
1126  {
1127  if((ActionVectorEntryPtr->Command != "Frh") && (ActionVectorEntryPtr->Command != "Frh-sh"))
1128  {
1129  RemainHereLogNotSent = true;
1130  }
1132  {
1133  // ignore TimeLoc & TTLoc departures
1134  // Action logs given in functions
1136  LastActionTime + TDateTime(30.0 / 86400)))
1137  {
1138  if(ActionVectorEntryPtr->Command == "fsp")
1139  {
1140  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1141  //'this' train. Next clock cycle will deal with any required changes
1142  FrontTrainSplit(0);
1143  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1144  { //this is checked before each return
1145  TrainHasFailed(0);
1146  }
1147  Utilities->CallLogPop(2041);
1148  return;
1149  }
1150  else if(ActionVectorEntryPtr->Command == "rsp")
1151  {
1152  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1153  //'this' train. Next clock cycle will deal with any required changes
1154  RearTrainSplit(0);
1155  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1156  {
1157  TrainHasFailed(1);
1158  }
1159  Utilities->CallLogPop(2042);
1160  return;
1161  }
1162  else if(ActionVectorEntryPtr->Command == "Fjo")
1163  {
1164  FinishJoin(0);
1165  }
1166  else if(ActionVectorEntryPtr->Command == "jbo")
1167  {
1168  JoinedBy(0);
1169  }
1170  else if(ActionVectorEntryPtr->Command == "cdt")
1171  {
1172  ChangeTrainDirection(0, false);
1173  }
1174  else if(ActionVectorEntryPtr->Command == "dsc")
1175  {
1176  Description = ActionVectorEntryPtr->NewDescription; //changed at v2.16.1
1180  }
1181  else if(ActionVectorEntryPtr->Command == "cms") //added at v2.21.0 for change of max speed
1182  {
1187  }
1188  else if(ActionVectorEntryPtr->Command == "Fns")
1189  {
1190  NewTrainService(0, false);
1191  }
1192  else if(ActionVectorEntryPtr->Command == "Frh")
1193  {
1194  RemainHere(0);
1195  }
1196  else if(ActionVectorEntryPtr->Command == "Fer")
1197  {
1198  TimetableFinished = true;
1199  }
1200  // other aspects of 'Fer' dealt with in TTrain::HasTrainGone()
1201  else if(ActionVectorEntryPtr->Command == "F-nshs")
1202  {
1204  }
1205  else if(ActionVectorEntryPtr->Command == "Frh-sh")
1206  {
1207  RepeatShuttleOrRemainHere(0, false);
1208  }
1209  else if(ActionVectorEntryPtr->Command == "Fns-sh")
1210  {
1212  }
1213 /*
1214  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
1215  shuttle headcode (no train creation)
1216  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1217  remain here
1218  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1219  form new service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
1220 */
1221  }
1222  }
1223  else
1224  {
1226  {
1228  }
1229  }
1230  }
1231  if(TrainMode == Timetable)
1232  {
1233  if(StoppedAtBuffers)
1234  {
1235  // error if buffers (& element before it) not at a location, or if buffer location different to ActionVectorEntryPtr location
1236  // if buffer location same as ActionVectorEntryPtr location & not Frh then error will be given for inability to depart
1237  AnsiString BufferLocation = Track->TrackElementAt(604, LeadElement).ActiveTrackElementName;
1238  if(BufferLocation == "")
1239  {
1241  }
1242  AnsiString ExpectedLocation = ActionVectorEntryPtr->LocationName;
1243  if((BufferLocation == "") || (BufferLocation != ExpectedLocation))
1244  {
1248  {
1250  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1251  // Drop missed actions so user can still use sig mode to get back on track
1253  }
1254  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1255  {
1257  TrainHasFailed(2);
1258  }
1259  Utilities->CallLogPop(1020);
1260  return;
1261  }
1262  else if((BufferLocation != "") && (BufferLocation == ExpectedLocation) && DepartureTimeSet && !RevisedStoppedAtLoc() && (TrainController->TTClockTime >
1263  ReleaseTime))
1264  {
1267  {
1270  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1271  // Drop missed actions so user can still use sig mode to get back on track
1273  }
1274  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1275  {
1277  TrainHasFailed(3);
1278  }
1279  Utilities->CallLogPop(1397);
1280  return;
1281  }
1282  }
1283  else
1284  {
1286  }
1287  }
1288  else
1289  {
1291  }
1292  if(TrainMode == Timetable)
1293  {
1295  {
1297  }
1299  {
1301  }
1302  }
1303  // Pick up element next to the train front (if exists) to check for calling-on, restart after a cleared signal, or
1304  // restart after stopped for train in front
1305  int NextElementPosition, NextEntryPos;
1306 
1307  if(LeadElement > -1) // if an exit continuation then not set
1308  {
1309  if((Track->TrackElementAt(186, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1310  {
1312  }
1313  else if((Track->TrackElementAt(187, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1314  {
1315  if(Track->TrackElementAt(188, LeadElement).Attribute == 0)
1316  {
1317  LeadExitPos = 1;
1318  }
1319  else
1320  {
1321  LeadExitPos = 3;
1322  }
1323  }
1324  NextElementPosition = Track->TrackElementAt(189, LeadElement).Conn[LeadExitPos];
1325  NextEntryPos = Track->TrackElementAt(190, LeadElement).ConnLinkPos[LeadExitPos];
1326  }
1327  else
1328  {
1329  NextElementPosition = -1;
1330  NextEntryPos = -1;
1331  }
1332  if((NextElementPosition > -1) && (NextEntryPos > -1))
1333  // may be buffers or continuation so need this check
1334  {
1335 /*
1336  Check whether calling-on conditions met:-
1337  a) approaching train has stopped at a signal but not at a location;
1338  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
1339  change of direction (cdt), remaining here (Frh), or under signaller control);
1340  c) at least 1 platform available for the approaching train;
1341  d) points (if any) set for direct route into platform;
1342  e) approaching train is to stop at station;
1343  f) no more facing signals between train and platform;
1344  g) [dropped g]
1345  h) train in front preventing route being set far enough to release stop signal;
1346  i) train in front not exiting at continuation;
1347  j) signal must be within 4km of the stop platform;
1348  k) [dropped (k), now can set a reoute or part route into platform so can set points more easily];
1349  l) no existing route conflicts with the route into the platform; and
1350  m) not failed or without power (these added at v2.10.0)
1351  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or
1352  other route conflicts - if a partial route set than can still change points outside the route or have a route conflict if another route is set.
1353 */
1354  if(TrainMode == Timetable)
1355  {
1356  if(CallingOnAllowed(0)) //returns false if failed or no power (modified afer v2.9.2)
1357  {
1358  CallingOnFlag = true;
1359  PlotTrainWithNewBackgroundColour(1, clCallOnBackground, Display); // calling-on background
1360  }
1361  else
1362  {
1363  if(CallingOnFlag) //TrainHasFailed sets this flag to false (at v2.10.0)
1364  {
1365  if(!TrainFailed) //shouldn't be needed but include for safety at v2.10.0
1366  {
1368  }
1369  }
1370  CallingOnFlag = false;
1371  }
1372  }
1373  if(StoppedAtSignal && ((Track->TrackElementAt(191, NextElementPosition).Attribute > 0) || AllowedToPassRedSignal) && !TrainFailed && !RevisedStoppedAtLoc())
1374  {
1375  //'&& !StoppedAtLocation' added at v2.7.0 as if had been stopped at signal before tt control restored then background colour changed to normal when signal changed from red
1376  // reset PassRedSignal when reached half-way point in next element, if reset here then SetTrainMovementValues
1377  // sets StoppedAtSignal again & train doesn't move
1378  StoppedAtSignal = false;
1379  // need to recalculate exit times since old entry time expired. Straddle now at MidLag with front of train on MidElement
1380  // hence use MidElement for the calculation so same as would have been used if signal not red, when Straddle was
1381  // LeadMidLag and front of train was on LeadElement (after the current move)
1383  EntrySpeed = 0;
1385  FirstHalfMove = true;
1386  SetTrainMovementValues(4, NextElementPosition, NextEntryPos);
1387  // NextElement is the element to be entered
1388  }
1389  if((LeadElement > -1) && (LeadExitPos > -1))//this section added at v2.18.0
1390  {
1391  int NextPos = Track->TrackElementAt(649, LeadElement).Conn[LeadExitPos];
1392  if(NextPos > -1)
1393  {
1394  int NextEntryPos = Track->TrackElementAt(1674, LeadElement).ConnLinkPos[LeadExitPos];
1395  if(Track->OtherTrainOnTrack(1, NextPos, NextEntryPos, TrainID))
1396  // true if another train on NextEntryPos track whether bridge or not
1397  {
1398  TrainInFront = true;
1399  }
1400  else
1401  {
1402  TrainInFront = false;
1403  }
1404  }
1405  }
1406  if((TrainMode == Signaller) && StoppedForTrainInFront && !TrainInFront) //train not directly in front //added at v2.23.0 without this a train under signaller control
1407  { //won't move until a train in front has passed the next signal, this is ok for being called on but under sig control the train should be able to move when required to do so
1408  StoppedForTrainInFront = false;
1409  }
1411  {
1412  if(ClearToNextSignal(0))
1413  {
1414  StoppedForTrainInFront = false;
1415  TrainInFront = false;
1416  BeingCalledOn = false;
1417  EntrySpeed = 0;
1419  FirstHalfMove = true;
1420  SetTrainMovementValues(16, NextElementPosition, NextEntryPos);
1421  }
1422  }
1423  }
1424  if(Stopped() && TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1425  {
1426  TrainHasFailed(4);
1427  Utilities->CallLogPop(1097);
1428  return;
1429  }
1430  if((Straddle == MidLag) && (LeadElement != -1))
1431  // later check only for Straddle == LeadMid, so need this check here for initial train start
1432  {
1434  }
1435 /* Logic below as follows: This check is made to allow a restart if had StoppedAtLocation or StoppedForTrainInFront or
1436  both but potentially able to restart (i.e. not at buffers, not crashed, not derailed, not held at location, departure
1437  time due, no train in front now & no other stop condition). Note that can be StoppedForTrainInFront when not at a
1438  location since this is set in SetTrainMovementValues whenever a train has zero EntrySpeed and there is a train in front,
1439  which could be when start as Snt.
1440  If StoppedForTrainInFront but not StoppedAtLocation then need to set TRSTime high so pink not plotted, and ReleaseTime
1441  low so can restart if appropriate. BeingCalledOn was set so that when train stopped at a station it wouldn't restart
1442  until the line was clear of trains up to the next signal. Hence check whether BeingCalledOn & if so set
1443  StoppedForTrainInFront, this ensures two things - that the restart check is carried out at each cycle and also that
1444  a restart won't happen until the line is clear to the next signal, regardless of whether or not the ReleaseTime has been
1445  reached.
1446  Then check if TRS time reached & change background to pink if so, & check if release time reached & if so change
1447  background to white and clear StoppedAtLocation. Then make check of station name, and recheck StoppedForTrainInFront,
1448  if it's set check if ClearToNextSignal and if so clear StoppedForTrainInFront & BeingCalledOn. If not ClearToNextSignal
1449  then return. If either not StoppedForTrainInFront or ClearToNextSignal then restart, calling SetTrainMovementValues &
1450  sending a message to the performancelog.
1451 */
1452 
1453  if(TrainMode == Timetable)
1454  {
1456  {
1457 // if(BeingCalledOn) //dropped when added TrainInFront at v2.18.0
1458 // {
1459 // TrainInFront = true;
1460 // }
1461  SetTrainMovementValues(25, LeadElement, LeadEntryPos); //this is purely to set StoppedForTrainInFront as needed before formal call //added at v2.19.1
1462  if((TrainController->TTClockTime >= TRSTime) && (PowerAtRail >= 1) && !StoppedForTrainInFront) //added later conditions at v2.19.1
1463  {
1465  }
1466  else //added at v2.14.0 as if a train ready to depart (pink b'gnd) taken under sig control then restored to tt control b'gnd stayed pink,
1467  { //even though release time now 30 seconds after tt control restored
1469  }
1471  {
1472  // value updated at every scheduled departure & arrival
1473  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0 moved here from after 'StoppedAtLocation = false' at v2.19.1
1474  {
1475  StoppedWithoutPower = true;
1476  }
1477  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1, don't change colour if no power or train in front
1478  {
1480  }
1481  AnsiString StationName;
1483  {
1485  }
1487  {
1489  }
1490  else
1491  {
1492  throw Exception("Error - Stopped at through station but neither lead nor mid elements have a name");
1493  }
1494  EntrySpeed = 0;
1496  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1 & all below put in conditional block - due to JasonB false departure log in email of 21/02/24
1497  { //= extended it to StoppedForTrainInFront as well for same reasons
1498  ZeroPowerDepartMessage = false;
1499  TrainInFrontMessage = false;
1500  FirstHalfMove = true;
1501  StoppedAtLocation = false;
1502  if((NextElementPosition > -1) && (NextEntryPos > -1))
1503  // condition check added for SloughIECC error reported by James U
1504  {
1505  if((Track->TrackElementAt(720, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1506  (Track->TrackElementAt(721, NextElementPosition).Attribute == 0))
1507  {
1508  StoppedAtSignal = true;
1510  // TrainController->LogActionError(40, HeadCode, "", SignalHold, Track->TrackElementAt(755, NextElementPosition).ElementID);
1511  }
1512  }
1514  {
1515  TimeTimeLocArrived = false;
1516  LogAction(27, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->DepartureTime, false);
1517  // no warning for TimeTimeLoc departure
1518  }
1519  else if(TreatPassAsTimeLocDeparture) //added at v2.12.0 so late/early/on time mins recorded accurately
1520  {
1521  LogAction(36, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->EventTime, ActionVectorEntryPtr->Warning); //EventTime because the real event is a pass
1522  }
1523  else //must be TimeLoc departure
1524  {
1526  }
1527  TreatPassAsTimeLocDeparture = false; //added at v2.12.0, reset after train departs
1528  ActualArrivalTime = TDateTime(0); //added these 3 at v2.23.0 //reset to zero, note that only run through this section once per arrival
1529  ArrivalMinDwellTime = 30.0; //reset to default value
1530  NonDefaultMinDwellTimeFlag = false; //reset
1531  DepartureTimeSet = false;
1532  // no need to set LastActionTime for a departure
1533  //deal here with departure pointer change, increment if SkippedDeparture
1534  CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //only add these after late mins added (in LogAction)
1535 
1536  if(SkippedDeparture) //only deal with this when have power
1537  {
1540  TrainSkippedEvents = 0;
1541  SkippedDeparture = false;
1542  SkipPtrValue = 0;
1543  ActionsSkippedFlag = false;
1544  }
1545  else
1546  {
1548  }
1549  // advance pointer beyond departure action - (this line (& LogAction) used to be at the end - see
1550  // note
1551 /*
1552  Note: If train stops at station after call on with a TimeTimeLoc loaded, and before the normal stop point, then when
1553  SetTrainMovementValues called it assumes a stop at the stop point because the ActionVectorEntryPtr points to a name
1554  when NameInTimetableBeforeCDT is called and the stop positions are valid. So next element train movement is based on
1555  this calculation. However, when the departure time check is made (it is during this function when SetTrainMovementValues
1556  is called), the ActionVectorEntryPtr is advanced at the end past the departure location, so at the next element when
1557  SetTrainMovementValues is called again, all is normal, i.e. the train doesn't stop again at the location. But to cure
1558  the problem move the ActionVectorEntryPtr increment to before SetTrainMovementValues.
1559 */
1561  {
1562  StoppedAtBuffers = true;
1563  }
1564  else
1565  // if buffers or no power, don't set values
1566  {
1568  {
1569  SetTrainMovementValues(12, NextElementPosition, NextEntryPos);
1570  // NextElement is the element to be entered
1571  }
1572  else
1573  {
1575  // use LeadElement for an exit continuation
1576  }
1577  }
1578  }
1579  else if(StoppedForTrainInFront) // StoppedForTrainInFront, don't advance AVPtr - added at v2.19.1
1580  {
1581  if(!TrainInFrontMessage)
1582  {
1583  TrainController->LogActionError(67, HeadCode, "", FailTrainInFront, StationName);
1584  TrainInFrontMessage = true;
1585  }
1586  }
1587  else //no power, don't advance AVPtr - added at v2.19.1
1588  {
1590  {
1592  ZeroPowerDepartMessage = true;
1593  }
1594  }
1595 
1596  }
1597  }
1598  }
1599  if(Straddle == LeadMidLag) //train on a half element
1600  {
1602  {
1603  Utilities->CallLogPop(654);
1604  return;
1605  }
1606  }
1607  else //train fully on 2 elements
1608  {
1610  {
1611  Utilities->CallLogPop(655);
1612  return;
1613  }
1614  }
1615  if((LeadElement > -1) && (MidElement > -1))
1616  {
1618  {
1619  // don't allow to stop if exiting at a continuation as causes problems if try to change direction
1620  // if entering at continuation & LeadElement is a continuation then MidElement will be -1
1621  //don't need to check for MidElement being continuation because popup menu won't show when exiting at continuation so SignallerStoppingFlag can't be set
1622  SignallerStoppingFlag = false;
1623  StepForwardFlag = false;
1624  }
1625  }
1626  if(Stopped())
1627  // this is what prevents another movement if the train is stopped
1628  {
1629  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1630  {
1631  TrainHasFailed(5);
1632  }
1633  BrakeRate = 0;
1634  Utilities->CallLogPop(656);
1635  return;
1636  }
1637 
1638  // HERE WHEN READY FOR NEXT MOVE
1639 
1640  //added at v2.10.0 to set SPADFlag if red signal immediately ahead (as it will be if in a locked route)
1641  //check if due to run past a red signal & if so set SPADFlag (SetTrainMovementValues & its SPAD check only called when arrive fully on 2 elements)
1642  if(Straddle == LeadMid) //fully on 2 elements
1643  {
1644  if(LeadElement > -1)
1645  {
1646  if(Track->TrackElementAt(1402, LeadElement).Conn[LeadExitPos] > -1)
1647  {
1649  if(TIF.TrackType == SignalPost)
1650  {
1651  int TIFEntryPos = Track->TrackElementAt(1405, LeadElement).ConnLinkPos[LeadExitPos];
1652  int TIFExitPos = 0;
1653  if(TIFEntryPos == 0)
1654  {
1655  TIFExitPos = 1;
1656  }
1657  if((TIF.Config[TIFExitPos] == Signal) && TIF.Attribute == 0 && (ExitSpeedHalf > 1) && !AllowedToPassRedSignal && !TIF.CallingOnSet) //use ExitSpeedHalf as may have been stopped at signal so entryspeed is 0
1659  {
1660  SPADFlag = true; // user has to intervene to reset & restart after spad
1661  }
1662  }
1663  }
1664  }
1665  }
1666 
1667  // check for train in front & if so stop at next access (when train fully on element next to train)
1668  if((TrainMode == Signaller) && (Straddle == LeadMidLag))
1669  // SetTrainMovementValues brakes & stops signaller mode train for a train in front using local
1670  // variable TrainInFrontInSignallerModeFlag
1671  {
1672  if((LeadElement > -1) && (LeadExitPos > -1))
1673  {
1674  int NextPos = Track->TrackElementAt(1672, LeadElement).Conn[LeadExitPos];
1675  int NextEntryPos = Track->TrackElementAt(1675, LeadElement).ConnLinkPos[LeadExitPos];
1676  if(Track->OtherTrainOnTrack(16, NextPos, NextEntryPos, TrainID))
1677  // true if another train on NextEntryPos track whether bridge or not
1678  {
1679  TrainInFront = true;
1680  }
1681  else
1682  {
1683  TrainInFront = false;
1684  }
1685  }
1686  }
1687  if((Straddle == LeadMid) && SPADFlag)
1688  // give message + plot background when ready to move half past the signal
1689  {
1690  if(NextElementPosition > -1)
1691  {
1692  if((Track->TrackElementAt(662, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1693  (Track->TrackElementAt(663, NextElementPosition).Attribute == 0))
1694  {
1695  AnsiString LocID = AnsiString(Track->TrackElementAt(664, NextElementPosition).ElementID);
1697  // if goes past 2 signals then give message twice
1699  }
1700  }
1701  }
1702  if(Straddle == LeadMidLag)
1703  // During this function train moves fully onto 2 elements, Lead & Mid, so set next 2 moves from here for the element after Lead
1704  {
1705  // if SPADFlag set allow to keep moving until signal obscured before setting background colour, & stop only when ExitSpeedFull is 0
1706  if(SPADFlag)
1707  {
1708  if(ExitSpeedFull == 0)
1709  {
1710  StoppedAfterSPAD = true;
1711  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1712  }
1713  }
1715  {
1716  if(ExitSpeedFull == 0)
1717  {
1718  // only reach here when will stop on LeadMid, because SetTrainMovementValues called after this (i.e. ExitSpeedFull becomes 0 if not 0 now
1719  // after this test), and Straddle == LeadMidLag so not accessed at the half-move point, hence only reached at the full move
1720  // point when the speed is 0. So, colour change won't occur until fully stopped (early in UpdateTrain()), and the log message
1721  // is sent at the right time and once only.
1722  SignallerStopped = true;
1723  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1724  StepForwardFlag = false;
1725  SignallerStoppingFlag = false;
1726  TTrackElement TE;
1727  AnsiString Loc = "";
1728  bool LocNamed = false;
1729  if(LeadElement > -1)
1730  {
1731  TE = Track->TrackElementAt(782, LeadElement);
1732  if(TE.ActiveTrackElementName != "")
1733  {
1734  Loc = TE.ActiveTrackElementName;
1735  LocNamed = true;
1736  }
1737  else
1738  {
1739  Loc = "track element " + TE.ElementID;
1740  }
1741  }
1742  if((MidElement > -1) && !LocNamed)
1743  {
1744  TE = Track->TrackElementAt(783, MidElement);
1745  if(TE.ActiveTrackElementName != "")
1746  {
1747  Loc = TE.ActiveTrackElementName;
1748  LocNamed = true;
1749  }
1750  else if(Loc == "")
1751  {
1752  Loc = "track element " + TE.ElementID;
1753  }
1754  }
1755  if(Loc == "")
1756  {
1757  Loc = "outside railway";
1758  // must have stopped after left at a continuation (because both lead & mid == -1)
1759  }
1760  else
1761  {
1762  Loc = "at " + Loc;
1763  }
1764  LogAction(30, HeadCode, "", SignallerStop, Loc, "", TrainController->TTClockTime, false); // false for warning
1765  }
1766  }
1767  if(LeadElement > -1) // if an exit continuation then not set
1768  {
1769  if((Track->TrackElementAt(202, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1770  {
1772  }
1773  else if((Track->TrackElementAt(203, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1774  {
1775  if(Track->TrackElementAt(204, LeadElement).Attribute == 0)
1776  {
1777  LeadExitPos = 1;
1778  }
1779  else
1780  {
1781  LeadExitPos = 3;
1782  }
1783  }
1784  NextElementPosition = Track->TrackElementAt(205, LeadElement).Conn[LeadExitPos];
1785  NextEntryPos = Track->TrackElementAt(206, LeadElement).ConnLinkPos[LeadExitPos];
1786  }
1787  else
1788  {
1789  NextElementPosition = -1;
1790  NextEntryPos = -1;
1791  }
1794  FirstHalfMove = true; //will be when finished the move onto 2 elements during this function
1795 
1796  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
1797  {
1798  StoppedWithoutPower = true;
1799  }
1800  if((NextElementPosition > -1) && (NextEntryPos > -1) && !SPADFlag)
1801  // may be buffers or continuation. SPADFlag added at v2.1.0
1802  // so don't override the SPAD colour & don't set StoppedAtSignal
1803  {
1804  if((Track->TrackElementAt(207, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1805  (Track->TrackElementAt(208, NextElementPosition).Attribute == 0) && (ExitSpeedFull < 1) && !RevisedStoppedAtLoc())
1806  {
1807  StoppedAtSignal = true;
1808  if(!StoppedWithoutPower)
1809  // leave background as is if no power, but set StoppedAtSignal
1810  {
1812  }
1813  // TrainController->LogActionError(41, HeadCode, "", SignalHold, Track->TrackElementAt(756, NextElementPosition).ElementID);
1814  }
1815  }
1816  if(!Stopped())
1817  {
1818  if((NextElementPosition > -1) && (NextEntryPos > -1))
1819  // may be buffers or continuation (skip SetTrainMovementValues if buffers, if
1820  // a stop element that isn't buffers - e.g. station, then will skip the calcs
1821  // during SetTrainMovementValues to avoid trying to divide by zero - see that
1822  // function for fuller explanation
1823  {
1824  SetTrainMovementValues(8, NextElementPosition, NextEntryPos);
1825  // NextElement is the element to be entered
1826  }
1827  // follow the continuation exits:-
1828  else if((LeadElement > -1) && (Track->TrackElementAt(209, LeadElement).TrackType == Continuation))
1829  {
1831  // Use LeadElement for calcs if lead is a continuation
1832  }
1833  else if((MidElement > -1) && (Track->TrackElementAt(210, MidElement).TrackType == Continuation))
1834  {
1836  // Use MidElement for calcs if mid is a continuation
1837  }
1838  else if((LagElement > -1) && (Track->TrackElementAt(211, LagElement).TrackType == Continuation))
1839  {
1841  // Use LagElement for calcs if lag is a continuation
1842  }
1843  }
1844  // remove route elements if not autosigs - this section moved from below, was under LagElement > -1 condition but needs to cover LagElement == -1
1845  if((AllRoutes->GetRouteTypeAndGraphics(2, LeadElement, LeadEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute))
1846  // Trains may not be in a route
1847  // Since Straddle = LeadMidLag at this point the train is going to move fully off the existing Lag & fully onto existing Lead element during this function
1848  {
1849  // NB if LeadElement == -1 then the above test returns NoRoute
1850  int TempH = Track->TrackElementAt(213, LeadElement).HLoc;
1851  int TempV = Track->TrackElementAt(214, LeadElement).VLoc;
1852  int TempELink = Track->TrackElementAt(215, LeadElement).Link[LeadEntryPos];
1853  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1854  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(10, TempH, TempV, SecondPair);
1855  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(143, FirstPair.first).GetFixedPrefDirElementAt(153,
1856  FirstPair.second).GetELink() == TempELink))
1857  {
1858  AllRoutes->RemoveRouteElement(10, TempH, TempV, TempELink);
1859  }
1860  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(144, SecondPair.first).GetFixedPrefDirElementAt(154,
1861  SecondPair.second).GetELink() == TempELink))
1862  {
1863  AllRoutes->RemoveRouteElement(11, TempH, TempV, TempELink);
1864  }
1865  }
1866  if(AllRoutes->GetRouteTypeAndGraphics(3, MidElement, MidEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1867  // Trains may not be in a route
1868  {
1869  int TempH = Track->TrackElementAt(216, MidElement).HLoc;
1870  int TempV = Track->TrackElementAt(217, MidElement).VLoc;
1871  int TempELink = Track->TrackElementAt(218, MidElement).Link[MidEntryPos];
1872  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1873  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(11, TempH, TempV, SecondPair);
1874  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(145, FirstPair.first).GetFixedPrefDirElementAt(155,
1875  FirstPair.second).GetELink() == TempELink))
1876  {
1877  AllRoutes->RemoveRouteElement(12, TempH, TempV, TempELink);
1878  }
1879  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(146, SecondPair.first).GetFixedPrefDirElementAt(156,
1880  SecondPair.second).GetELink() == TempELink))
1881  {
1882  AllRoutes->RemoveRouteElement(13, TempH, TempV, TempELink);
1883  }
1884  }
1885  if(AllRoutes->GetRouteTypeAndGraphics(4, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1886  // Trains may not be in a route
1887  {
1888  int TempH = Track->TrackElementAt(219, LagElement).HLoc;
1889  int TempV = Track->TrackElementAt(220, LagElement).VLoc;
1890  int TempELink = Track->TrackElementAt(221, LagElement).Link[LagEntryPos];
1891  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1892  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(12, TempH, TempV, SecondPair);
1893  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(147, FirstPair.first).GetFixedPrefDirElementAt(157,
1894  FirstPair.second).GetELink() == TempELink))
1895  {
1896  AllRoutes->RemoveRouteElement(14, TempH, TempV, TempELink);
1897  }
1898  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(148, SecondPair.first).GetFixedPrefDirElementAt(158,
1899  SecondPair.second).GetELink() == TempELink))
1900  {
1901  AllRoutes->RemoveRouteElement(15, TempH, TempV, TempELink);
1902  }
1903  AllRoutes->CheckMapAndRoutes(8); // test
1904  }
1905  if(LagElement > -1)
1906  // not entering at a continuation so can deal with train leaving the lag element
1907  {
1909  // amended below so route elements removed for the complete train (for NotAutoSigsRoutes), so train never standing on a route once it
1910  // starts moving, covers for eliminating route when train reaches buffers, and prevents odd route segments when route extended while
1911  // straddling 3 elements (formerly the last segment was replotted as a route & stayed plotted
1912 
1913  TPrefDirElement PrefDirElement;
1914  // plot locked route marker for any element if appropriate (i.e. if a locked AutoSigs route) but only when train leaves element completely
1915  // as this is a 16x16 graphic
1917  {
1919  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
1920  }
1921  if(ContinuationExit(2, LagElement, LagExitPos)) // true if Element is a continuation and Exitpos is the continuation end
1922  {
1923  int RouteNumber;
1924  TrainGone = true;
1925  // flag to indicate train to be deleted - outside this function
1927  {
1928  TTrainController::TContinuationAutoSigEntry ContinuationAutoSigEntry;
1929  ContinuationAutoSigEntry.RouteNumber = RouteNumber;
1930  // calc distance from & inc last signal to exit
1931  int LastElement = LagElement, LastExitPos = LagExitPos, CumDistance = 0;
1932  int NewLastElement = 0, NewLastExitPos = 0;
1933  // need above because can't change LastElement & LastExitPos until both new values obtained
1934  // while((Track->TrackElementAt(684, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200)) as was
1935  while((Track->TrackElementAt(913, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200) && (Track->TrackElementAt(897,
1936  LastElement).TrackType != Points))
1937  // extra condition above added because of Moric1998's error (see email of 24/03/2016), where had an autosigs route across points, and another continuation on track not occupied by route so
1938  // failed when found a new element = -1 when tried to cross the continuation. Note this routine can only deal with non points as it uses GetNonPointsOppositeLinkPos
1939  // leave CumDistance as it was in these circumstances.
1940  {
1941  if(LastExitPos < 2)
1942  {
1943  CumDistance += Track->TrackElementAt(685, LastElement).Length01;
1944  }
1945  else
1946  {
1947  CumDistance += Track->TrackElementAt(686, LastElement).Length23;
1948  }
1949  NewLastElement = Track->TrackElementAt(687, LastElement).Conn[Track->GetNonPointsOppositeLinkPos(LastExitPos)];
1950  if(NewLastElement == -1)
1951  // this will catch buffers or any other connection failure
1952  {
1953  break; //throw Exception("Error, Connection = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of
1954  } //10/02/23, had two continuations linked with no signal between
1955  NewLastExitPos = Track->TrackElementAt(688, LastElement).ConnLinkPos[Track->GetNonPointsOppositeLinkPos(LastExitPos)]; //so when train exited this routine tracked
1956  if(NewLastExitPos == -1) //back to the entry continuation which had no further connection - doesn't need to be an error at all!
1957  {
1958  break; //throw Exception("Error, ConnLinkPos = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of 10/02/23 , see above
1959  }
1960  LastElement = NewLastElement;
1961  LastExitPos = NewLastExitPos;
1962  }
1963  // if at signal add this in too (may not be signal if 'break;' encountered but doesn't matter)
1964  if(CumDistance < 1200)
1965  {
1966  CumDistance += Track->TrackElementAt(689, LastElement).Length01; // only need 01 for signal
1967  }
1968  // now have distance including the signal, if >=1200m use 100m (for a signal immediately after the continuation)
1969  // else use 1200m - CumDistance
1970  int FirstDistance = 0;
1971  if(CumDistance >= 1200)
1972  {
1973  FirstDistance = 100;
1974  }
1975  else
1976  {
1977  FirstDistance = 1200 - CumDistance;
1978  }
1979  if(FirstDistance < 100)
1980  {
1981  FirstDistance = 100; // don't allow < 100
1982  }
1983  // can now calc the time delays in seconds - FirstDelay, SecondDelay & ThirdDelay, these are doubles
1984  // BUT - first check whether ExitSpeedFull is very low (Mark had divide by zero error with zero exit speed using v2.4.0)
1985  if(ExitSpeedFull > 20.0)
1986  {
1987  ContinuationAutoSigEntry.FirstDelay = 3.6 * double(FirstDistance) / ExitSpeedFull;
1988  // speed in km/h & distance in m so mult by 3.6 to bring to secs
1989  ContinuationAutoSigEntry.SecondDelay = ContinuationAutoSigEntry.FirstDelay + 4320.0 / ExitSpeedFull;
1990  // 4320.0 = 3.6 * 1200, .0 to make it a double
1991  ContinuationAutoSigEntry.ThirdDelay = ContinuationAutoSigEntry.SecondDelay + 4320.0 / ExitSpeedFull;
1992  }
1993  else
1994  {
1995  ContinuationAutoSigEntry.FirstDelay = 60.0; // 60 secs between each action
1996  ContinuationAutoSigEntry.SecondDelay = 120.0;
1997  ContinuationAutoSigEntry.ThirdDelay = 180.0;
1998  }
1999  ContinuationAutoSigEntry.AccessNumber = 0;
2000  ContinuationAutoSigEntry.PassoutTime = TrainController->TTClockTime;
2002  {
2004  for(VectorIT = TrainController->ContinuationAutoSigVector.begin(); VectorIT != TrainController->ContinuationAutoSigVector.end();
2005  VectorIT++)
2006  {
2007  if(VectorIT->RouteNumber == RouteNumber)
2008  {
2009  // another train has passed out of same route so erase earlier entry
2010  TrainController->ContinuationAutoSigVector.erase(VectorIT);
2011  break;
2012  }
2013  }
2014  }
2015  TrainController->ContinuationAutoSigVector.push_back(ContinuationAutoSigEntry);
2016  }
2018  // need to plot this as returning early so will miss the later plot (not a bridge so don't need PlotAlternativeTrackRouteGraphic)
2019  Display->Update();
2020  // need to keep this since Update() not called for PlotSmallOutput as too slow
2021  Utilities->CallLogPop(659);
2022  return;
2023  }
2024  // above covers for exiting at continuation, need XLinkPos check to exclude entering at a continuation
2025  if(LeadElement > -1)
2026  {
2027  TTrackElement &TE = Track->TrackElementAt(224, LeadElement); //added at v2.13.0 for brevity
2028  if(TE.Config[LeadExitPos] == Signal)
2029  // changed to lead so reset early
2030  {
2031  LastSigPassedFailed = false; //used to cancel route elements up to next signal for autosigs route
2032  TE.Attribute = 0; // red
2033  int RouteNumber; //only used for autosigs routes
2034  //add chance to fail when train passes a signal
2035  if((random(Utilities->SignalChangeEventsPerFailure) == 0) && !TE.Failed && (Utilities->FailureMode != FNil) &&
2036  (TrainMode == Timetable) && !TE.CallingOnSet) //can't fail twice, calling on signal can't fail
2037  {
2039  IFE.TVPos = LeadElement;
2040  TE.Failed = true;
2041  Display->WarningLog(19, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": Signal failed at " + TE.ElementID);
2042  PerfLogForm->PerformanceLog(42, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: Signal failed at " + TE.ElementID);
2043  TrainController->StopTTClockMessage(129, "Signal at " + TE.ElementID +
2044  " failed when changing aspect.\nTrains can only pass under signaller control.");
2045  AllRoutes->RebuildRailwayFlag = true; //force ClearandRebuildRailway at next clock tick
2046  LastSigPassedFailed = true;
2047  //set repair time, random value in minutes between 10 and 179
2048  double FailureMinutes = double(random(Utilities->MaxRandomRepairTime) + Utilities->FixedMinRepairTime); //between 10 and 179 minutes at random
2049  TDateTime RepairTime = TrainController->TTClockTime + TDateTime(FailureMinutes / 1440);
2050  IFE.RepairTime = RepairTime;
2052  Track->FailedSignalsVector.push_back(IFE); //rearwards signals will be set when LagElement leaves signal
2053  }
2054  TE.CallingOnSet = false;
2055  // don't plot if zoomed out
2056  if(!Display->ZoomOutFlag)
2057  {
2059  }
2060  // covers signal resetting in same direction
2061  }
2062  }
2064  {
2065  AllRoutes->RebuildRailwayFlag = true; //added at v2.13.0 to replot signal after train left in case it had failed
2066  if(AllRoutes->GetRouteTypeAndGraphics(5, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2067  {
2068  Display->PlotOutput(23, Track->TrackElementAt(227, LagElement).HLoc * 16, Track->TrackElementAt(228, LagElement).VLoc * 16, EXGraphicPtr);
2069  Display->PlotOutput(24, Track->TrackElementAt(229, LagElement).HLoc * 16, Track->TrackElementAt(230, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2070  TPrefDirElement PrefDirElement;
2071  // plot locked route marker for same side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic
2073  {
2075  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2076  }
2078  LockedVectorNumber)))
2079  {
2081  }
2082  }
2083  }
2084  else if((LeadElement > -1) && (Track->TrackElementAt(233, LeadElement).TrackType == SignalPost))
2085  {
2086  Track->TrackElementAt(234, LeadElement).Attribute = 0; // red
2088  // don't plot if zoomed out
2089  if(!Display->ZoomOutFlag)
2090  {
2092  }
2093  // covers signal passed in opposite direction - replot as red, regardless of what it was before, though should already have been red
2094  }
2096  {
2097  if(AllRoutes->GetRouteTypeAndGraphics(6, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2098  {
2099  Display->PlotOutput(26, Track->TrackElementAt(236, LagElement).HLoc * 16, Track->TrackElementAt(237, LagElement).VLoc * 16, EXGraphicPtr);
2100  Display->PlotOutput(27, Track->TrackElementAt(238, LagElement).HLoc * 16, Track->TrackElementAt(239, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2101  // below added at v1.3.0 to reset signals if back out of an autosigs route under signaller control after changing direction, when new LeadElement not on route (if it had
2102  // been the route would have been ForceCancelled). Note that the signal is not facing the direction of travel else would have entered
2103  // "if(Track->TrackElementAt(, LagElement).Config[LagExitPos] == Signal)" above and wouldn't be here
2104  int RouteNumber;
2106  // already know it's an autosigsroute, this is just to get the RouteNumber
2107  // addition below at v1.3.2 - found that a signal that had reached double yellow in ContinuationAutoSigs was reset to red when a following train's lag element
2108  // moved off a signal in the normal course of events. It was caused when a train backed out of an autosigs route under signaller control after changing
2109  // direction (see DevHistory.txt). Hence check that the train is in signaller mode and that the train's lead element isn't on the same route before calling SetRouteSignals.
2110  int RouteNumber2;
2112  // already know it's an autosigsroute, this is just to get the RouteNumber
2113  if((TrainMode == Signaller) && (RouteNumber2 != RouteNumber))
2114  // note that if not in a route (as likely) then RouteNumber2 set to -1
2115  {
2116  AllRoutes->GetFixedRouteAt(217, RouteNumber).SetRouteSignals(10);
2117  // this was in the 1.3.0 addition but without the condition
2118  }
2119  // end of 1.3.2 addition
2120  // end of 1.3.0.addition
2121  }
2122  TPrefDirElement PrefDirElement;
2123  // plot locked route marker for opp side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic (OK - Straddle == LeadMidLag)
2125  {
2127  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2128  }
2129  }
2130  }
2131  }
2132  // straddle ONLY changed here, check if 'LeadMid' first & if so ready for updating Elements
2133  if(Straddle == LeadMid) //about to move half onto next element
2134  {
2135  AllowedToPassRedSignal = false;
2136  // if had been allowed to pass then at this point it will move half onto signal so can be reset
2137  // if(LagElement > -1) ResetTrainElementID(LagElement, LagEntryPos);//train fully off old LagElement so can clear TrainOnElement flags - no, reset at earlier call when lag moves off element
2138  if(DerailPending)
2139  // set during last GetLeadElement, but only act on it when train fully on offending point
2140  // i.e. next time Straddle reaches LeadMid
2141  {
2142  Derailed = true;
2143  DerailPending = false;
2147  Utilities->CallLogPop(657);
2148  return;
2149  }
2156  Straddle = MidLag;
2157  // train now fully on the updated Lag & Mid, the front segment is going to move onto the new
2158  // LeadElement during this function (note that if stopped at signal then won't get this far)
2159  if(LeadElement > -1)
2160  {
2162  // i.e an exit continuation only
2163  // if don't exclude entry continuations then can't progress past it
2164  {
2165  LeadElement = -1;
2166  }
2167  else
2168  {
2169  GetLeadElement(0);
2170  // sets or resets DerailPending & StoppedAtSignal, and sets LeadElement values
2172  if(Stopped())
2173  {
2174  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
2175  {
2176  TrainHasFailed(6);
2177  }
2178  Utilities->CallLogPop(658);
2179  return; // i.e. don't move forward one step if next element is a red signal
2180  }
2181  }
2182  }
2183  }
2184  if(LagElement > -1)
2185  {
2186  // below are the actions required at both half moves for LagElement > -1
2188 
2189  // if was in locked route but has timed out when train leaves then plot the normal track graphic over the route graphic that is
2190  // still in BackgroundGraphic[3], if wasn't in a route then will just replot the same BackgroundGraphic
2191  // need to do this for each half element
2192 
2193  TPrefDirElement PrefDirElement;
2194  if(!(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(7, LagElement, LagExitPos, PrefDirElement, LockedVectorNumber)))
2195  {
2196  int RouteNumber; // holder for call below - not used
2198  {
2199  if(Utilities->clTransparent == TColor(0xFFFFFF))
2200  // change to black for a white background
2201  {
2203  // only applies for AutoSigs Route in case was locked & timed out
2204  }
2205  else
2206  // change to white for a dark background
2207  {
2209  // only applies for AutoSigs Route in case was locked & timed out
2210  }
2212  }
2213  }
2215  // above in case train just moving off a bridge & either alternative track in a route - need to keep its route colour,
2216  // or a train on the opposite track - needs to be replotted
2217  }
2218  // update all array values
2219  HOffset[3] = HOffset[2];
2220  HOffset[2] = HOffset[1];
2221  HOffset[1] = HOffset[0];
2222  VOffset[3] = VOffset[2];
2223  VOffset[2] = VOffset[1];
2224  VOffset[1] = VOffset[0];
2225  Graphics::TBitmap *TempPtr = BackgroundPtr[3];
2226 
2227  BackgroundPtr[3] = BackgroundPtr[2];
2228  BackgroundPtr[2] = BackgroundPtr[1];
2229  BackgroundPtr[1] = BackgroundPtr[0];
2230  BackgroundPtr[0] = TempPtr;
2231 
2232  // update headcode graphics depending on Lead entry value
2233  if(LeadElement > -1) // if Lead is -1 then stays as is
2234  {
2236  {
2237  for(int x = 0; x < 4; x++)
2238  {
2239  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
2240  }
2241  }
2242  else
2243  {
2244  for(int x = 0; x < 4; x++)
2245  {
2247  }
2248  }
2249  }
2250  if(TrainMode == Timetable)
2251  {
2253  }
2254  else
2255  {
2257  }
2259 
2260  // plot new seg [0] on Lead & [2] on Mid ([2] always on Mid)
2261  if(LeadElement > -1)
2262  {
2263  if(Straddle == MidLag)
2264  // just about to move half onto the new lead element
2265  {
2267  // pick up new background bitmap [0]
2269  int LeadElementTrainID = Track->TrackElementAt(244, LeadElement).TrainIDOnElement;
2270  if((LeadElementTrainID > -1) && (LeadElementTrainID != TrainID))
2271  // check if own ID for entry at continuation, else crashes into itself!
2272  {
2273  // OK if crossing on a bridge
2274  int OtherTrainEntryPos = TrainController->EntryPos(0, LeadElementTrainID, LeadElement);
2275  if(OtherTrainEntryPos == -1)
2276  {
2277  throw Exception("Error - OtherTrainEntryPos not set");
2278  }
2279  if((Track->TrackElementAt(246, LeadElement).TrackType != Bridge) || (LeadEntryPos == OtherTrainEntryPos) ||
2280  // LeadEntryPos for rear end crashes
2281  (LeadExitPos == OtherTrainEntryPos))
2282  // LeadExitPos for head-on crashes
2283  {
2285  Crashed = true; // only set if Straddle = MidLag
2286  CallingOnFlag = false;
2287  // in case was set, need to disable call on if call on button had been pressed
2288  }
2289  }
2290  else if(MidElement > -1) // will be -1 for continuation entries
2291  {
2292  // check if about to move onto a crossing diagonal that is occupied by another train, and if so crash
2293  int MidExitLinkNum = Track->TrackElementAt(889, MidElement).Link[MidExitPos];
2294  int MidHLoc = Track->TrackElementAt(890, MidElement).HLoc;
2295  int MidVLoc = Track->TrackElementAt(891, MidElement).VLoc;
2296  int OtherTrainID = -1;
2297  if((MidExitLinkNum == 1) || (MidExitLinkNum == 3) || (MidExitLinkNum == 7) || (MidExitLinkNum == 9))
2298  {
2299  if(Track->DiagonalFouledByTrain(0, MidHLoc, MidVLoc, MidExitLinkNum, OtherTrainID))
2300  {
2301  TrainCrashedInto = OtherTrainID;
2302  Crashed = true; // only set if Straddle = MidLag
2303  CallingOnFlag = false;
2304  // in case was set, need to disable call on if call on button had been pressed
2305  }
2306  }
2307  }
2308  }
2309  else
2310  {
2312  // pick up new background bitmap [0]
2314  }
2315  PlotElement[0] = LeadElement;
2317  PlotTrainGraphic(12, 0, Display);
2318  }
2319  if(MidElement > -1)
2320  {
2321  PlotElement[2] = MidElement;
2323  PlotTrainGraphic(1, 2, Display);
2324  }
2325  // plot the new positions for [1] & [3] graphics - [1] on Mid if Straddle = MidLag, on Lead if Straddle = LeadMidLag
2326  // [3] on Lag if Straddle = MidLag, on Mid if Straddle = LeadMidLag
2327  if(Straddle == MidLag)
2328  {
2329  if(MidElement > -1)
2330  {
2331  PlotElement[1] = MidElement;
2333  PlotTrainGraphic(2, 1, Display);
2334  }
2335  if(LagElement > -1)
2336  {
2337  PlotElement[3] = LagElement;
2339  PlotTrainGraphic(3, 3, Display);
2340  }
2341  }
2342  else // Straddle == LeadMidLag
2343  {
2344  if(LeadElement > -1)
2345  {
2346  PlotElement[1] = LeadElement;
2348  PlotTrainGraphic(4, 1, Display);
2349  }
2350  if(MidElement > -1)
2351  {
2352  PlotElement[3] = MidElement;
2354  PlotTrainGraphic(5, 3, Display);
2355  }
2356  }
2357  if(Crashed)
2358  // only reach here if crash into another train, if crash into buffers or an LC then return earlier at the if(Stopped()) test
2359  {
2364  // in case was set, need to disable call on if call on button had been pressed
2371  Straddle = LeadMidLag;
2372  // was MidLag but plotted as LeadMidLag so change Straddle accordingly
2373  Display->Update();
2374  // resurrected when Update() dropped from PlotOutput etc
2375  Utilities->CallLogPop(660);
2376  return;
2377  }
2378  // deal here with station stops & pass times after all replotting done but before Straddle changed
2379  if(TrainMode == Timetable)
2380  {
2381  if(Straddle == LeadMidLag)
2382  {
2383  if((LeadElement > -1) && (MidElement > -1) && !TimetableFinished)
2384  {
2385  // NameInTimetableBeforeCDT returns the number by which the train ActionVectorEntryPtr needs to be incremented
2386  // to point to the location arrival entry - before a change of direction
2387  AnsiString LocName = Track->TrackElementAt(249, LeadElement).ActiveTrackElementName;
2388  bool StopRequired = false;
2389  int TTVPos = NameInTimetableBeforeCDT(1, LocName, StopRequired); //excludes continuations
2390  if(TTVPos > -1) // -1 if can't find it or if name is ""
2391  {
2392  // check if at buffers (no, dropped buffer check to allow to crash into buffers) or a through station stop,
2393  // or a station where next element contains a train or a stop signal, if so
2394  // stop now, note that for 2nd check, if next element is a bridge then will have stopped by now so no need
2395  // to test the actual track the train is on since it can't be a platform
2396  TTrackElement LeadTrackElement = Track->TrackElementAt(258, LeadElement);
2397  TTrackElement NextTrackElement; // default for now
2398  bool TrainAtStopLinkPos1 = (LeadTrackElement.StationEntryStopLinkPos1 == LeadEntryPos);
2399  bool TrainAtStopLinkPos2 = (LeadTrackElement.StationEntryStopLinkPos2 == LeadEntryPos);
2400  bool TrainAtStopLinkPos3 = (LeadTrackElement.StationEntryStopLinkPos3 == LeadEntryPos);
2401  bool TrainAtStopLinkPos4 = (LeadTrackElement.StationEntryStopLinkPos4 == LeadEntryPos);
2402  bool ForwardConnection = (LeadTrackElement.Conn[LeadExitPos] > -1);
2403  int NextElementEntryPos = -1;
2404  int NextElementExitPos = -1;
2405  bool TrainOnNextElement = false;
2406  bool StopSignalAtNextElement = false;
2407  if(ForwardConnection)
2408  // if no forward connection can't derive anything from it without errors
2409  {
2410  NextTrackElement = Track->TrackElementAt(262, LeadTrackElement.Conn[LeadExitPos]);
2411  NextElementEntryPos = LeadTrackElement.ConnLinkPos[LeadExitPos];
2412  NextElementExitPos = Track->GetNonPointsOppositeLinkPos(NextElementEntryPos);
2413  // this is only for signals so no need to worry about points ambiguity
2414  TrainOnNextElement = (NextTrackElement.TrainIDOnElement > -1);
2415  StopSignalAtNextElement = ((NextTrackElement.Config[NextElementExitPos] == Signal) && (NextTrackElement.Attribute == 0));
2416  }
2417  if(TrainAtStopLinkPos1 || TrainAtStopLinkPos2 || TrainAtStopLinkPos3 || TrainAtStopLinkPos4 || (ForwardConnection && (TrainOnNextElement || StopSignalAtNextElement)))
2418  {
2419  if(TTVPos > 0)
2420  {
2422  ActionVectorEntryPtr += TTVPos;
2423  }
2424  if(StopRequired) //this is where ActualArrivalTime & ArrivalMinDwellTime are set for both TimeLoc Arrivals and TimeTimeLocs
2425  { //note that the above values are retained for all finish service/start new service events
2426  StoppedAtLocation = true;
2427  StoppedAtSignal = false;
2428  // may have been set earlier at line 925 so need to reset as
2429  // StoppedAtLocation takes precedence and don't want both set at same time or have flashing graphic
2430  // in zoom out mode
2431  if(!TrainFailed)
2432  {
2434  // pale green
2435  }
2436  ActualArrivalTime = TrainController->TTClockTime; //added at v2.13.0
2437  ArrivalMinDwellTime = ActionVectorEntryPtr->MinDwellTime; //in order to retrieve MinDwellTime when reach departure
2438  if(ArrivalMinDwellTime > 30.1) //i.e. not the default value
2439  {
2441  }
2443  {
2444  TimeTimeLocArrived = true;
2445  // used in case of later signaller control, when need to know
2446  // whether had arrived or not, to avoid sending the arrival
2447  // message twice, see TInterface::TimetableControl1Click
2448  }
2450  }
2451  else
2452  {
2454  }
2456  {
2458  }
2459  // don't alter ActionVectorEntryPtr if at a TimeTimeLoc (& can't be anything else other than TimeLoc or PassTime after calling NameInTimetableBeforeCDT successfully)
2461  }
2462  }
2463  }
2464  }
2465  }
2466  if(Straddle == MidLag)
2467  {
2468  Straddle = LeadMidLag;
2469  FirstHalfMove = false;
2470  }
2471  else if(Straddle == LeadMidLag)
2472  {
2473  Straddle = LeadMid;
2474  FirstHalfMove = true;
2475  }
2476  else if(Straddle == LeadMid)
2477  {
2478  throw Exception("Error, Straddle shouldn't be LeadMid prior to resetting at exit from UpdateTrain");
2479  }
2480  if(TrainFailurePending) // ok, moving but PlotElements set above
2481  {
2482  TrainHasFailed(7);
2483  }
2485  Display->Update();
2486  // need to keep this since Update() not called for PlotSmallOutput as too slow
2487  Utilities->CallLogPop(661);
2488 }
2489 
2490 // ----------------------------------------------------------------------------
2491 
2493 {
2494  if((!Display->ZoomOutFlag) && (TrainDataEntryPtr->ServiceReference.Length() > 4))
2495  {
2497  {
2499  }
2500  if(Plotted && (LeadElement > -1) && Utilities->ShowLongServRefsFlag) //if not plotted yet, exiting at continuation, or show flag false then ignore
2501  {
2502  EnterLongServRefAsName(0, Disp);
2503  }
2504  }
2505 }
2506 
2507 // ----------------------------------------------------------------------------
2508 
2509 void TTrain::EnterLongServRefAsName(int Caller, TDisplay *Disp) //added at v2.22.0 to display long serv refs
2510 { //colours clB1G0R0 for white bgnd, clB3G5R5 for dark bgnds
2511 
2512 /* There are several parts to this function as follows:
2513 StaticFeaturesDisplay & StaticfeaturesScreen are created in the TInterface constructor (and destroyed in the destructor).
2514 The display is a Display object like Display and HiddenDisplay, and has HiddenDisplay assigned to it almost at the end of Clearand...
2515 just before the trains are plotted. It is used to remove the text of the long serv. ref. prior to replotting it after the train has moved.
2516 The colours for the text and the font are set in the TTrainController constructor according to the background colour.
2517 There are two small bitmaps LongServRefNameBitmap (54 x 13 pixels) and LongServRefWorkingBitmap (54 x 10 pixels), the former to hold the
2518 long serv. ref. text on a transparent background, and the latter to hold the bitmap to be overlaid on the main display both for plotting
2519 and removal. The former is taller because when text is plotted there are 3 pixels at the top above the text that aren't used, so
2520 these are omitted for overlaying on the main display.
2521 StaticFeaturesDisplay is resized whenever the form is resized, and is cleared and filled with the transparent colour whenever OperMode or
2522 RestartOperMode selected.
2523 This function (EnterLongServRefAsName) calculates the text position (HPos & VPos, saved in LongServRefTextH & LongServRefTextV) for removal, which may be a long time
2524 after plotting. DisplayOffsetH & V are static values so apply to all three displays, and are used to ensure the correct text removal
2525 graphic is picked up from the StaticFeaturesScreen - picking anything up outside this area is blank.
2526 The text of the long serv. ref. uses unique colours (for white & dark backgrounds) so these can be identified when plotting over the main
2527 display graphic using the scanline function - LongServRefNameBitmap is examined starting 3 pixels down and where a pixel uses the relevant
2528 text colour-number (using the websafe colour palette) it is transferred onto LongServRefWorkingBitmap so the name overlies anything already there,
2529 then LongServRefWorkingBitmap drawn onto the main display at the correct position (it uses draw instead of CopyRect as that respects
2530 transparency. After plotting LongServRefWorkingBitmap is loaded from the same position in StaticFeaturesDisplay, then where LongServRefNameBitmap
2531 contains a text coloured pixel a near-transparent colour is plotted onto LongServRefWorkingBitmap in its place. Where any other colour including
2532 transparent appears it is plotted without change. A near-transparent colour is used so it won't show against the existing background, the transparent
2533 colour can't be used as it would leave the text in place.
2534 */
2535 
2536  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EnterLongServRefAsName");
2537  if(LongServRefEnteredFlag) //if already present then return
2538  {
2539  Utilities->CallLogPop(2724);
2540  return;
2541  }
2542  int VPos, HPos, TrainHLocLead, TrainHLocMid, TrainHLocLag, TrainVLocLead, TrainVLocMid, TrainVLocLag;
2543  TTrackElement TrainLead, TrainMid, TrainLag;
2544  bool ReturnEarly = false;
2545 
2546  //set all H & VPos's
2547  if(LeadElement > -1)
2548  {
2549  TrainLead = Track->TrackElementAt(1686, LeadElement);
2550  }
2551  else
2552  {
2553  ReturnEarly = true;
2554  }
2555  if(MidElement > -1)
2556  {
2557  TrainMid = Track->TrackElementAt(1687, MidElement);
2558  }
2559  else
2560  {
2561  ReturnEarly = true;
2562  }
2563  if(LagElement > -1)
2564  {
2565  TrainLag = Track->TrackElementAt(1688, LagElement);
2566  }
2567  else if(Straddle == LeadMidLag)
2568  {
2569  ReturnEarly = true;
2570  }
2571  if(ReturnEarly)
2572  {
2573  Utilities->CallLogPop(2725);
2574  return;
2575  }
2576 
2577  int LeftHAndOffset, TopVAndOffset;
2578 //get leftmost and topmost train graphic elements
2579 
2580  if(Straddle == LeadMid)
2581  {
2582  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2583  if(((TrainLead.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2584  {
2585  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[1];
2586  }
2587  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2588  {
2589  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2590  }
2591  if(((TrainMid.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2592  {
2593  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[3];
2594  }
2595  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2596  if(((TrainLead.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2597  {
2598  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[1];
2599  }
2600  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2601  {
2602  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2603  }
2604  if(((TrainMid.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2605  {
2606  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[3];
2607  }
2608  HPos = LeftHAndOffset;
2609  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2610  }
2611  else if(Straddle == LeadMidLag)
2612  {
2613  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2614  if(((TrainMid.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2615  {
2616  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[1];
2617  }
2618  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2619  {
2620  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2621  }
2622  if(((TrainLag.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2623  {
2624  LeftHAndOffset = (TrainLag.HLoc * 16) + HOffset[3];
2625  }
2626  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2627  if(((TrainMid.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2628  {
2629  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[1];
2630  }
2631  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2632  {
2633  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2634  }
2635  if(((TrainLag.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2636  {
2637  TopVAndOffset = (TrainLag.VLoc * 16) + VOffset[3];
2638  }
2639  HPos = LeftHAndOffset;
2640  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2641  }
2642  else //shouldn't be anything else
2643  {
2644  Utilities->CallLogPop(2726);
2645  return;
2646  }
2647 
2648  LongServRefTextH = HPos; //have to store these so text can be erased using absolute location
2649  LongServRefTextV = VPos;
2650 
2651  //write service reference to bitmap after clearing it, can't do it in constructor as service ref not set at that stage
2652  LongServRefNameBitmap->Transparent = true;
2653  LongServRefNameBitmap->TransparentColor = Utilities->clTransparent;
2654  LongServRefNameBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2655  LongServRefNameBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2656  LongServRefNameBitmap->Canvas->FillRect(TRect(0,0,54,13)); //fill it with transparent colour
2657  LongServRefNameBitmap->Canvas->Font->Assign(TrainController->LongServRefFont); //assign all font properties
2658  LongServRefNameBitmap->Canvas->TextOut(2, 0, TrainDataEntryPtr->ServiceReference);
2659 
2660 //Display->GetImage()->Picture->Bitmap->Canvas->CopyRect(TRect(0,0,54,13), LongServRefNameBitmap->Canvas, TRect(0,0,54,13));
2661 
2662  //copy MainScreen background segment but clear it first
2663  LongServRefWorkingBitmap->Transparent = true;
2664  LongServRefWorkingBitmap->TransparentColor = Utilities->clTransparent;
2665  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2666  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2667  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2668 
2669  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2671 
2672 //set up ImageLongServRefBitmap ready for receiving name & on white background
2673  ImageLongServRefBitmap->Transparent = true;
2674  ImageLongServRefBitmap->TransparentColor = clB5G5R5; //white
2675  ImageLongServRefBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2676  ImageLongServRefBitmap->Canvas->Brush->Color = clB5G5R5; //white
2677  ImageLongServRefBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with white
2678 
2679  Byte *SLPtrIn; // pointer to the ScanLine values in LongServRefNameBitmap
2680  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2681  Byte *SLPtrImage; // pointer to the ScanLine values in ImageLongServRefBitmap
2682 
2683  for(int x = 3; x < 13; x++)
2684  {
2685  SLPtrIn = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]);
2686  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]);
2687  SLPtrImage = reinterpret_cast<Byte*>(ImageLongServRefBitmap->ScanLine[x - 3]);
2688  for(int y = 0; y < 54; y++)
2689  {
2690  if(SLPtrIn[y] == TrainController->LongServRefFontColNumber)
2691  {
2692  SLPtrOut[y] = SLPtrIn[y];
2693  SLPtrImage[y] = 0x01; //clB1G0R0
2694  } //else do nothing
2695  }
2696  }
2697 
2698 //ImageLongServRefBitmap now set up ready to write to image if required
2699 
2700 //copy back onto MainScreen - use Draw to retain transparent pixels CopyRect doesn't accept transparent pixels
2702 
2703 //from here building a new LongServRefWorkingBitmap ready for the next removal
2704  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2705  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2706  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2707  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2709  LongServRefTextV - (Display->DisplayOffsetV * 16) + 10));
2710 //now just get the background pixels where overlap with text
2711 
2712 //Disp->GetImage()->Canvas->CopyRect(TRect(0,0,200,200), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2713 // LongServRefTextV - (Display->DisplayOffsetV * 16), LongServRefTextH - (Display->DisplayOffsetH * 16) + 200,
2714 // LongServRefTextV - (Display->DisplayOffsetV * 16) + 200));
2715 
2716  byte *SLPtrText;
2717  for(int x = 3; x < 13; x++)
2718  {
2719  SLPtrText = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]); //this is 13 pixels deep
2720  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]); //this is 10 pixels deep
2721  for(int y = 0; y < 54; y++)
2722  {
2723  if(SLPtrText[y] == TrainController->LongServRefFontColNumber)
2724  {
2725  if(SLPtrOut[y] == TrainController->BgndColNumber)
2726  {
2728  } //else leave pixel as it is
2729  } //else leave pixel as it is
2730  }
2731  }
2732 
2733 //Disp->GetImage()->Canvas->CopyRect(TRect(0,210, 54,220), LongServRefWorkingBitmap->Canvas, TRect(0,0,54,10));
2734 
2735  Disp->Update();
2736  LongServRefEnteredFlag = true;
2737  Utilities->CallLogPop(2727);
2738 }
2739 
2740 // ----------------------------------------------------------------------------
2741 
2742 void TTrain::RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp) //added at v2.22.0 to remove long serv refs
2743 {
2744  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RemoveLongServRef," + NameText);
2745  if(!LongServRefEnteredFlag) //if already absent then return
2746  {
2747  Utilities->CallLogPop(2728);
2748  return;
2749  }
2750 
2751 //copy back onto MainScreen, but first avoid overwriting any existing leading train colours (grey [clB4G4R4], red [clB0G0R5], or blue [clB5G0R0])
2752 //Pick up the relevant section of the main display onto a new bitmap
2753  Graphics::TBitmap *TempBitmap = new Graphics::TBitmap;
2754  TempBitmap->PixelFormat = pf8bit;
2755  TempBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
2756  TempBitmap->Width = 54;
2757  RailGraphics->SetWebSafePalette(64, TempBitmap);
2758  TempBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2760 
2761  Byte *SLPtrIn; // pointer to the ScanLine values in TempBitmap
2762  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2763  for(int x = 0; x < 10; x++)
2764  {
2765  SLPtrIn = reinterpret_cast<Byte*>(TempBitmap->ScanLine[x]);
2766  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x]);
2767  for(int y = 0; y < 54; y++)
2768  {
2769  if(SLPtrIn[y] == 0xac) //this is clB4G4R4 - grey
2770  {
2771  SLPtrOut[y] = TrainController->BgndColNumber;
2772  }
2773  else if(SLPtrIn[y] == 0xb4) //this is clB0G0R5 - red for leading character
2774  {
2775  SLPtrOut[y] = TrainController->BgndColNumber;
2776  }
2777  else if(SLPtrIn[y] == 0x05) //this is clB5G0R0 - blue for signaller control leading character
2778  {
2779  SLPtrOut[y] = TrainController->BgndColNumber;
2780  }
2781  }
2782  }
2783 
2785  Disp->Update();
2786  LongServRefEnteredFlag = false;
2787  delete TempBitmap;
2788  Utilities->CallLogPop(2729);
2789 }
2790 
2791 // ----------------------------------------------------------------------------
2792 
2793 Graphics::TBitmap *TTrain::SetOneGraphicCode(char CodeChar)
2794 {
2795  switch(CodeChar)
2796  {
2797  case '0':
2798  return(RailGraphics->Code0);
2799 
2800  case '1':
2801  return(RailGraphics->Code1);
2802 
2803  case '2':
2804  return(RailGraphics->Code2);
2805 
2806  case '3':
2807  return(RailGraphics->Code3);
2808 
2809  case '4':
2810  return(RailGraphics->Code4);
2811 
2812  case '5':
2813  return(RailGraphics->Code5);
2814 
2815  case '6':
2816  return(RailGraphics->Code6);
2817 
2818  case '7':
2819  return(RailGraphics->Code7);
2820 
2821  case '8':
2822  return(RailGraphics->Code8);
2823 
2824  case '9':
2825  return(RailGraphics->Code9);
2826 
2827  case 'A':
2828  return(RailGraphics->CodeA);
2829 
2830  case 'B':
2831  return(RailGraphics->CodeB);
2832 
2833  case 'C':
2834  return(RailGraphics->CodeC);
2835 
2836  case 'D':
2837  return(RailGraphics->CodeD);
2838 
2839  case 'E':
2840  return(RailGraphics->CodeE);
2841 
2842  case 'F':
2843  return(RailGraphics->CodeF);
2844 
2845  case 'G':
2846  return(RailGraphics->CodeG);
2847 
2848  case 'H':
2849  return(RailGraphics->CodeH);
2850 
2851  case 'I':
2852  return(RailGraphics->CodeI);
2853 
2854  case 'J':
2855  return(RailGraphics->CodeJ);
2856 
2857  case 'K':
2858  return(RailGraphics->CodeK);
2859 
2860  case 'L':
2861  return(RailGraphics->CodeL);
2862 
2863  case 'M':
2864  return(RailGraphics->CodeM);
2865 
2866  case 'N':
2867  return(RailGraphics->CodeN);
2868 
2869  case 'O':
2870  return(RailGraphics->CodeO);
2871 
2872  case 'P':
2873  return(RailGraphics->CodeP);
2874 
2875  case 'Q':
2876  return(RailGraphics->CodeQ);
2877 
2878  case 'R':
2879  return(RailGraphics->CodeR);
2880 
2881  case 'S':
2882  return(RailGraphics->CodeS);
2883 
2884  case 'T':
2885  return(RailGraphics->CodeT);
2886 
2887  case 'U':
2888  return(RailGraphics->CodeU);
2889 
2890  case 'V':
2891  return(RailGraphics->CodeV);
2892 
2893  case 'W':
2894  return(RailGraphics->CodeW);
2895 
2896  case 'X':
2897  return(RailGraphics->CodeX);
2898 
2899  case 'Y':
2900  return(RailGraphics->CodeY);
2901 
2902  case 'Z':
2903  return(RailGraphics->CodeZ);
2904 
2905  case 'a':
2906  return(RailGraphics->Code_a);
2907 
2908  case 'b':
2909  return(RailGraphics->Code_b);
2910 
2911  case 'c':
2912  return(RailGraphics->Code_c);
2913 
2914  case 'd':
2915  return(RailGraphics->Code_d);
2916 
2917  case 'e':
2918  return(RailGraphics->Code_e);
2919 
2920  case 'f':
2921  return(RailGraphics->Code_f);
2922 
2923  case 'g':
2924  return(RailGraphics->Code_g);
2925 
2926  case 'h':
2927  return(RailGraphics->Code_h);
2928 
2929  case 'i':
2930  return(RailGraphics->Code_i);
2931 
2932  case 'j':
2933  return(RailGraphics->Code_j);
2934 
2935  case 'k':
2936  return(RailGraphics->Code_k);
2937 
2938  case 'l':
2939  return(RailGraphics->Code_l);
2940 
2941  case 'm':
2942  return(RailGraphics->Code_m);
2943 
2944  case 'n':
2945  return(RailGraphics->Code_n);
2946 
2947  case 'o':
2948  return(RailGraphics->Code_o);
2949 
2950  case 'p':
2951  return(RailGraphics->Code_p);
2952 
2953  case 'q':
2954  return(RailGraphics->Code_q);
2955 
2956  case 'r':
2957  return(RailGraphics->Code_r);
2958 
2959  case 's':
2960  return(RailGraphics->Code_s);
2961 
2962  case 't':
2963  return(RailGraphics->Code_t);
2964 
2965  case 'u':
2966  return(RailGraphics->Code_u);
2967 
2968  case 'v':
2969  return(RailGraphics->Code_v);
2970 
2971  case 'w':
2972  return(RailGraphics->Code_w);
2973 
2974  case 'x':
2975  return(RailGraphics->Code_x);
2976 
2977  case 'y':
2978  return(RailGraphics->Code_y);
2979 
2980  case 'z':
2981  return(RailGraphics->Code_z);
2982 
2983  default:
2984  return(RailGraphics->TempHeadCode);
2985  }
2986 }
2987 
2988 // ----------------------------------------------------------------------------
2989 
2990 void TTrain::SetHeadCodeGraphics(int Caller, AnsiString Code)
2991 {
2992  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetHeadCodeGraphics," + HeadCode);
2993  if(Code.Length() != 4)
2994  {
2995  TrainController->StopTTClockMessage(62, "Headcode Incorrect length");
2996  }
2997  for(int x = 1; x < 5; x++) // AnsiString indices start at 1
2998  {
2999  HeadCodeGrPtr[x - 1]->Assign(SetOneGraphicCode(Code[x]));
3000  }
3001  if(BackgroundColour != clB5G5R5)
3002  // i.e. not the basic graphic colour as loaded from resource file
3003  {
3004  for(int x = 0; x < 4; x++)
3005  {
3007  }
3008  }
3009  Utilities->CallLogPop(1484);
3010 }
3011 
3012 // ----------------------------------------------------------------------------
3013 
3014 void TTrain::GetLeadElement(int Caller)
3015 // assumes Mid & Lag already set, sets LeadElement,
3016 // LeadEntryPos, LeadExitPos & DerailPending (don't want to act on it immediately)
3017 {
3018  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetLeadElement," + HeadCode);
3019  DerailPending = false;
3023  {
3024  // attr 0=straight, - links 0 & 1 (0 = lead)
3025  // attr 1=diverging, - links 2 & 3 (2 = lead)
3026  // set appropriate next element or derail - use a subroutine & return element & bool for derail
3027  // points always have links 0 & 2 = lead, link 1 = trailing straight, link 3 = training diverging
3028 
3029  // if enter at lead, exit at whatever attr set at
3030  // if enter at lag, exit at lead, but set derail wrt attribute
3031  if((LeadEntryPos == 0) && (Track->TrackElementAt(272, LeadElement).Attribute == 0))
3032  {
3033  LeadExitPos = 1;
3034  }
3035 
3036  // strictly speaking shouldn't need to set to 0 and 2 correctly since TrackIsInARoute caters for both, but
3037  // best to be on safe side
3038  else if(LeadEntryPos == 0)
3039  {
3040  LeadEntryPos = 2;
3041  LeadExitPos = 3;
3042  }
3043  else if((LeadEntryPos == 2) && (Track->TrackElementAt(273, LeadElement).Attribute == 0))
3044  {
3045  LeadEntryPos = 0;
3046  LeadExitPos = 1;
3047  }
3048  else if(LeadEntryPos == 2)
3049  {
3050  LeadExitPos = 3;
3051  }
3052  else if((LeadEntryPos == 1) && (Track->TrackElementAt(274, LeadElement).Attribute == 0))
3053  {
3054  LeadExitPos = 0;
3055  }
3056  else if(LeadEntryPos == 1)
3057  {
3058  LeadExitPos = 0;
3059  DerailPending = true;
3060  }
3061  else if((LeadEntryPos == 3) && (Track->TrackElementAt(275, LeadElement).Attribute == 0))
3062  {
3063  LeadExitPos = 0;
3064  DerailPending = true;
3065  }
3066  else if(LeadEntryPos == 3)
3067  {
3068  LeadExitPos = 0;
3069  }
3070  }
3071  else if(LeadEntryPos == 0)
3072  {
3073  LeadExitPos = 1;
3074  }
3075  else if(LeadEntryPos == 1)
3076  {
3077  LeadExitPos = 0;
3078  }
3079  else if(LeadEntryPos == 2)
3080  {
3081  LeadExitPos = 3;
3082  }
3083  else if(LeadEntryPos == 3)
3084  {
3085  LeadExitPos = 2;
3086  }
3087  // TTrackElement TrackElement = Track->TrackElementAt(276, LeadElement);
3088 /* signal check moved to Update() function
3089  if((TrackElement.TrackType == SignalPost) && (TrackElement.Config[LeadExitPos] == Signal)
3090  && (TrackElement.Attribute == 0))//0 = red
3091  {
3092  StoppedAtSignal = true; //comment out for test of locked route graphic replot
3093  }
3094  else
3095  {
3096  StoppedAtSignal = false;
3097  }
3098 */
3099  Utilities->CallLogPop(662);
3100 }
3101 
3102 // ----------------------------------------------------------------------------
3103 
3104 void TTrain::GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
3105 {
3106  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetOffsetValues," + AnsiString(Link) + "," + HeadCode);
3107  switch(Link)
3108  {
3109  case 1:
3110  {
3111  HOffset = 0;
3112  VOffset = 0;
3113  break;
3114  }
3115 
3116  case 2:
3117  {
3118  HOffset = 4;
3119  VOffset = 0;
3120  break;
3121  }
3122 
3123  case 3:
3124  {
3125  HOffset = 8;
3126  VOffset = 0;
3127  break;
3128  }
3129 
3130  case 4:
3131  {
3132  HOffset = 0;
3133  VOffset = 4;
3134  break;
3135  }
3136 
3137  case 6:
3138  {
3139  HOffset = 8;
3140  VOffset = 4;
3141  break;
3142  }
3143 
3144  case 7:
3145  {
3146  HOffset = 0;
3147  VOffset = 8;
3148  break;
3149  }
3150 
3151  case 8:
3152  {
3153  HOffset = 4;
3154  VOffset = 8;
3155  break;
3156  }
3157 
3158  case 9:
3159  {
3160  HOffset = 8;
3161  VOffset = 8;
3162  break;
3163  }
3164 
3165  default:
3166  {
3167  throw Exception("Error in GetOffsetValues - Link value wrong");
3168  }
3169  }
3170  Utilities->CallLogPop(674);
3171 }
3172 
3173 // ---------------------------------------------------------------------------
3174 
3175 bool TTrain::LowEntryValue(int EntryLink) const
3176 {
3177 /* returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i.e.
3178  the character that is red or blue) is the last character of the headcode, otherwise it's the first character of the headcode
3179 */
3180  if((EntryLink == 1) || (EntryLink == 2) || (EntryLink == 4) || (EntryLink == 7))
3181  {
3182  return(true);
3183  }
3184  else
3185  {
3186  return(false);
3187  }
3188 }
3189 
3190 // ---------------------------------------------------------------------------
3191 
3192 void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3193 {
3194  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) + "," +
3195  AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3196  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3197  // default values
3198  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3199 
3200  TAllRoutes::TRouteType RouteType;
3201 
3202  RouteType = AllRoutes->GetRouteTypeAndGraphics(11, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3203 
3204  TRect SourceRect, DestRect;
3205 
3206  DestRect.init(0, 0, 8, 8); // initialise left, top, right, bottom
3207  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3208  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3209  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap;
3210 
3211  TempGraphic->PixelFormat = pf8bit;
3212  TempGraphic->Width = 16;
3213  TempGraphic->Height = 16;
3214  TTrackElement TempElement = Track->TrackElementAt(286, Element);
3215 
3216  if(TempElement.TrackType == Points)
3217  {
3218  TempGraphic->Assign(TempElement.GraphicPtr);
3219  TempGraphic->Transparent = true;
3220  TempGraphic->TransparentColor = Utilities->clTransparent;
3221  if(RouteType == TAllRoutes::AutoSigsRoute)
3222  {
3223  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3224  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3225  }
3226  else
3227  {
3228  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3229  }
3230  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3231  }
3232  else if(TempElement.TrackType == GapJump) // plot set gap
3233  {
3234  if(TempElement.SpeedTag == 88)
3235  {
3236  TempGraphic->Assign(RailGraphics->gl88set);
3237  }
3238  else if(TempElement.SpeedTag == 89)
3239  {
3240  TempGraphic->Assign(RailGraphics->gl89set);
3241  }
3242  else if(TempElement.SpeedTag == 90)
3243  {
3244  TempGraphic->Assign(RailGraphics->gl90set);
3245  }
3246  else if(TempElement.SpeedTag == 91)
3247  {
3248  TempGraphic->Assign(RailGraphics->gl91set);
3249  }
3250  else if(TempElement.SpeedTag == 92)
3251  {
3252  TempGraphic->Assign(RailGraphics->gl92set);
3253  }
3254  else if(TempElement.SpeedTag == 93)
3255  {
3256  TempGraphic->Assign(RailGraphics->bm93set);
3257  }
3258  else if(TempElement.SpeedTag == 94)
3259  {
3260  TempGraphic->Assign(RailGraphics->bm94set);
3261  }
3262  else if(TempElement.SpeedTag == 95)
3263  {
3264  TempGraphic->Assign(RailGraphics->gl95set);
3265  }
3266  TempGraphic->Transparent = true;
3267  TempGraphic->TransparentColor = Utilities->clTransparent;
3268  if(RouteType == TAllRoutes::AutoSigsRoute)
3269  {
3270  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3271  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3272  }
3273  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3274  }
3275  // new for version 0.6
3276  else if(TempElement.TrackType == SignalPost)
3277  {
3278  if(TempElement.SigAspect == TTrackElement::GroundSignal)
3279  {
3280  for(int x = 0; x < 40; x++)
3281  {
3282  if((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3283  // need to stop aspect
3284  {
3285  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3286  break;
3287  }
3288  }
3289  }
3290  else // normal signal
3291  {
3292  TempGraphic->Assign(TempElement.GraphicPtr);
3293  // GraphicPtr set to normal signal in a signal track element
3294  }
3295  TempGraphic->Transparent = true;
3296  TempGraphic->TransparentColor = Utilities->clTransparent;
3297  if(RouteType == TAllRoutes::AutoSigsRoute)
3298  {
3299  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3300  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3301  }
3302  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3303  }
3304  else
3305  {
3306  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3307  // can't name points gaps or signals so 'else' OK
3308  bool FoundFlag;
3309  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3310  if(FoundFlag)
3311  {
3313  {
3314  GraphicPtr->Canvas->CopyRect(DestRect, Track->InactiveTrackElementAt(26, IMPair.first).GraphicPtr->Canvas, SourceRect);
3315  TempGraphic->Assign(RailGraphics->bmName);
3316  TempGraphic->Transparent = true;
3317  TempGraphic->TransparentColor = Utilities->clTransparent;
3318  if(RouteType == TAllRoutes::AutoSigsRoute)
3319  {
3320  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3321  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3322  }
3323  else
3324  {
3325  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3326  }
3327  // draw track on top
3328  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3329  }
3330  else if(Track->InactiveTrackElementAt(116, IMPair.first).TrackType == LevelCrossing)
3331  {
3332  TempGraphic->Assign(TempElement.GraphicPtr);
3333  TempGraphic->Transparent = true;
3334  TempGraphic->TransparentColor = Utilities->clTransparent;
3335  // note that can't be an AutoSigsRoute
3336  // now overlay the LC central portion
3337  int BDVectorPos = -1; //not used
3338  if(Track->AnyLinkedBarrierDownVectorManual(0, Track->InactiveTrackElementAt(130, IMPair.first).HLoc, Track->InactiveTrackElementAt(131, IMPair.first).VLoc, BDVectorPos))
3339  {
3340  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlainMan);
3341  }
3342  else
3343  {
3344  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3345  }
3346  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3347  }
3348  else
3349  {
3350  TempGraphic->Assign(TempElement.GraphicPtr);
3351  TempGraphic->Transparent = true;
3352  TempGraphic->TransparentColor = Utilities->clTransparent;
3353  if(RouteType == TAllRoutes::AutoSigsRoute)
3354  {
3355  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3356  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3357  }
3358  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3359  }
3360  }
3361  else
3362  {
3363  TempGraphic->Assign(TempElement.GraphicPtr);
3364  TempGraphic->Transparent = true;
3365  TempGraphic->TransparentColor = Utilities->clTransparent;
3366  if(RouteType == TAllRoutes::AutoSigsRoute)
3367  {
3368  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3369  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3370  }
3371  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3372  }
3373  }
3374  delete TempGraphic;
3375  Utilities->CallLogPop(675);
3376 }
3377 
3378 // ---------------------------------------------------------------------------
3379 
3380 // This was an attempt to pick up the actual 8x8 graphic from the display, so that text & user graphics would show as soon as the train passed, and overwrite it with the
3381 // reconstructed track, and it works ok but for the little arrows showing route directions at start and end, which extend beyond the track. It doesn't matter for autosig
3382 // routes because they are replotted (along with the direction arrows) but for others they shouldn't be. Leave in in case an easy way to remove these pointers comes to mind.
3383 /*
3384 
3385  void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3386  {
3387  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) +
3388  "," + AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3389  TAllRoutes::TRouteType RouteType;
3390  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3391  // default values
3392  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3393  TTrackElement TempElement = Track->TrackElementAt(, Element); //this is a copy of the element passed into the function
3394  TRect SourceRect, DestRect, ScreenSourceRect;
3395  RouteType = AllRoutes->GetRouteTypeAndGraphics(7, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3396 
3397  DestRect.init(0, 0, 8, 8); //initialise left, top, right, bottom
3398  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3399  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3400 
3401  //add text & user graphics if any to *GraphicPtr prior to adding the track
3402  int Left = ((TempElement.HLoc - Display->DisplayOffsetH) * 16) + HOffset;
3403  int Top = ((TempElement.VLoc - Display->DisplayOffsetV) * 16) + VOffset;
3404  int Right = Left + 8;
3405  int Bottom = Top + 8;
3406  ScreenSourceRect.init(Left, Top, Right, Bottom);
3407  GraphicPtr->Canvas->CopyMode = cmSrcCopy;
3408  GraphicPtr->Canvas->CopyRect(DestRect, Display->GetImage()->Canvas, ScreenSourceRect);
3409 
3410  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap; //this will hold the 16x16 reconstructed element, prior to transfer of the 8x8 bit to *GraphicPtr
3411  TempGraphic->PixelFormat = pf8bit;
3412  TempGraphic->Width = 16;
3413  TempGraphic->Height = 16;
3414 
3415  Graphics::TBitmap *SourceGraphic = new Graphics::TBitmap; //this will hold the 8x8 element segment from TempGraphic - needed because to keep transparency have to use Draw, not CopyRect
3416  SourceGraphic->PixelFormat = pf8bit;
3417  SourceGraphic->Width = 16;
3418  SourceGraphic->Height = 16;
3419  SourceGraphic->Transparent = true;
3420  SourceGraphic->TransparentColor = Utilities->clTransparent;
3421 
3422  if (TempElement.TrackType == Points)
3423  {
3424  TempGraphic->Assign(TempElement.GraphicPtr);
3425  TempGraphic->Transparent = true;
3426  TempGraphic->TransparentColor = Utilities->clTransparent;
3427  if (RouteType == TAllRoutes::AutoSigsRoute)
3428  {
3429  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3430  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3431  }
3432  else
3433  {
3434  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3435  }
3436  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3437  }
3438  else if (TempElement.TrackType == GapJump) // plot set gap
3439  {
3440  if (TempElement.SpeedTag == 88)
3441  TempGraphic->Assign(RailGraphics->gl88set);
3442  else if (TempElement.SpeedTag == 89)
3443  TempGraphic->Assign(RailGraphics->gl89set);
3444  else if (TempElement.SpeedTag == 90)
3445  TempGraphic->Assign(RailGraphics->gl90set);
3446  else if (TempElement.SpeedTag == 91)
3447  TempGraphic->Assign(RailGraphics->gl91set);
3448  else if (TempElement.SpeedTag == 92)
3449  TempGraphic->Assign(RailGraphics->gl92set);
3450  else if (TempElement.SpeedTag == 93)
3451  TempGraphic->Assign(RailGraphics->bm93set);
3452  else if (TempElement.SpeedTag == 94)
3453  TempGraphic->Assign(RailGraphics->bm94set);
3454  else if (TempElement.SpeedTag == 95)
3455  TempGraphic->Assign(RailGraphics->gl95set);
3456  TempGraphic->Transparent = true;
3457  TempGraphic->TransparentColor = Utilities->clTransparent;
3458  if (RouteType == TAllRoutes::AutoSigsRoute) {
3459  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3460  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3461  }
3462  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3463  }
3464  // new for version 0.6
3465  else if (TempElement.TrackType == SignalPost)
3466  {
3467  if (TempElement.SigAspect == TTrackElement::GroundSignal)
3468  {
3469  for (int x = 0; x < 40; x++)
3470  {
3471  if ((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3472  // need to stop aspect
3473  {
3474  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3475  break;
3476  }
3477  }
3478  }
3479  else // normal signal
3480  {
3481  TempGraphic->Assign(TempElement.GraphicPtr);
3482  // GraphicPtr set to normal signal in a signal track element
3483  }
3484  TempGraphic->Transparent = true;
3485  TempGraphic->TransparentColor = Utilities->clTransparent;
3486  if (RouteType == TAllRoutes::AutoSigsRoute) {
3487  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3488  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3489  }
3490  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3491  }
3492  else {
3493  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3494  // can't name points gaps or signals so 'else' OK
3495  bool FoundFlag;
3496  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap
3497  (4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3498  if (FoundFlag)
3499  {
3500  if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == NamedNonStationLocation)
3501  {
3502  GraphicPtr->Canvas->CopyRect(DestRect,
3503  Track->InactiveTrackElementAt(, IMPair.first).GraphicPtr->Canvas, SourceRect);
3504  TempGraphic->Assign(RailGraphics->bmName);
3505  TempGraphic->Transparent = true;
3506  TempGraphic->TransparentColor = Utilities->clTransparent;
3507  if (RouteType == TAllRoutes::AutoSigsRoute)
3508  {
3509  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3510  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3511  }
3512  else
3513  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3514  // draw track on top
3515  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3516  SourceRect);
3517  }
3518  else if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == LevelCrossing) {
3519  TempGraphic->Assign(TempElement.GraphicPtr);
3520  TempGraphic->Transparent = true;
3521  TempGraphic->TransparentColor = Utilities->clTransparent;
3522  // note that can't be an AutoSigsRoute
3523  // now overlay the LC central portion
3524  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3525  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3526  SourceRect);
3527  }
3528  else {
3529  TempGraphic->Assign(TempElement.GraphicPtr);
3530  TempGraphic->Transparent = true;
3531  TempGraphic->TransparentColor = Utilities->clTransparent;
3532  if (RouteType == TAllRoutes::AutoSigsRoute) {
3533  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3534  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3535  }
3536  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3537  SourceRect);
3538  }
3539  }
3540  else {
3541  TempGraphic->Assign(TempElement.GraphicPtr);
3542  TempGraphic->Transparent = true;
3543  TempGraphic->TransparentColor = Utilities->clTransparent;
3544  if (RouteType == TAllRoutes::AutoSigsRoute) {
3545  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3546  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3547  }
3548  SourceGraphic->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3549  GraphicPtr->Canvas->Draw(0, 0, SourceGraphic);
3550  }
3551  }
3552  delete TempGraphic;
3553  delete SourceGraphic;
3554  Utilities->CallLogPop();
3555  }
3556 */
3557 // ---------------------------------------------------------------------------
3558 
3559 void TTrain::PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
3560 {
3561  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainGraphic," + AnsiString(ArrayNumber) + "," + HeadCode);
3562  if(PlotElement[ArrayNumber] == -1)
3563  {
3564  Utilities->CallLogPop(676);
3565  return; // not plotted yet
3566  }
3567  SetTrainElementID(0, PlotElement[ArrayNumber], PlotEntryPos[ArrayNumber]);
3568  // set before plot so gap flashing stops first
3569  Disp->PlotOutput(29, ((Track->TrackElementAt(295, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3570  ((Track->TrackElementAt(296, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), HeadCodePosition[ArrayNumber]);
3571  // Only need to set ID for leading element, stays set until train finally leaves the element
3572  Plotted = true;
3573  Utilities->CallLogPop(677);
3574 }
3575 
3576 // ---------------------------------------------------------------------------
3577 
3578 void TTrain::PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
3579 {
3580  Disp->PlotOutput(30, ((Track->TrackElementAt(297, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3581  ((Track->TrackElementAt(298, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), BackgroundPtr[ArrayNumber]);
3582 }
3583 
3584 // ---------------------------------------------------------------------------
3585 
3586 bool TTrain::BufferAtExit(int Caller, int Element, int ExitPos) const
3587 {
3588  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BufferAtExit," + AnsiString(Element) + "," + AnsiString(ExitPos) + "," +
3589  HeadCode);
3590  if((Track->TrackElementAt(299, Element).TrackType == Buffers) && (Track->TrackElementAt(300, Element).Config[ExitPos] == End))
3591  {
3592  Utilities->CallLogPop(678);
3593  return(true);
3594  }
3595  else
3596  {
3597  Utilities->CallLogPop(679);
3598  return(false);
3599  }
3600 }
3601 
3602 // ---------------------------------------------------------------------------
3603 
3604 bool TTrain::ContinuationExit(int Caller, int Element, int ExitPos) const
3605 {
3606  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationExit," + AnsiString(Element) + "," + AnsiString(ExitPos) +
3607  "," + HeadCode);
3608  if((Track->TrackElementAt(301, Element).TrackType == Continuation) && (Track->TrackElementAt(302, Element).Config[ExitPos] == End))
3609  {
3610  Utilities->CallLogPop(680);
3611  return(true);
3612  }
3613  else
3614  {
3615  Utilities->CallLogPop(681);
3616  return(false);
3617  }
3618 }
3619 
3620 // ---------------------------------------------------------------------------
3621 
3622 bool TTrain::IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
3623 // test whether this train on a bridge on trackpos 0 & 1
3624 {
3625  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos01," + AnsiString(TrackVectorPosition) + "," +
3626  HeadCode);
3627  if(Track->TrackElementAt(303, TrackVectorPosition).TrackType != Bridge)
3628  {
3629  Utilities->CallLogPop(682);
3630  return(false);
3631  }
3632  // if(Track->TrackElementAt(304, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3634  {
3636  {
3637  throw Exception("Error, same train on two different bridge tracks");
3638  }
3639  else
3640  {
3641  Utilities->CallLogPop(684);
3642  return(true);
3643  }
3644  }
3645  else
3646  {
3647  Utilities->CallLogPop(685);
3648  return(false);
3649  }
3650 }
3651 
3652 // ---------------------------------------------------------------------------
3653 
3654 bool TTrain::IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
3655 // test whether this train on a bridge on trackpos 2 & 3
3656 {
3657  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos23," + AnsiString(TrackVectorPosition) + "," +
3658  HeadCode);
3659  if(Track->TrackElementAt(307, TrackVectorPosition).TrackType != Bridge)
3660  {
3661  Utilities->CallLogPop(686);
3662  return(false);
3663  }
3664  // if(Track->TrackElementAt(308, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3666  {
3667  // don't carry out check for train on tracks 0 & 1 else will enter an infinite loop if train on both
3668  Utilities->CallLogPop(687);
3669  return(true);
3670  }
3671  else
3672  {
3673  Utilities->CallLogPop(688);
3674  return(false);
3675  }
3676 }
3677 
3678 // ---------------------------------------------------------------------------
3679 
3680 void TTrain::SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3681 {
3682  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3683  AnsiString(EntryPos) + "," + HeadCode);
3684  Track->TrackElementAt(310, TrackVectorPosition).TrainIDOnElement = TrainID;
3685 
3686  // unplot GapFlash graphics if land on flashing gap (this done before train plotted - see PlotTrainGraphic)
3687  if(Track->GapFlashFlag)
3688  {
3690  {
3693  Track->GapFlashFlag = false;
3694  }
3695  }
3696  if(Track->TrackElementAt(311, TrackVectorPosition).TrackType == Bridge)
3697  {
3698  if(EntryPos == -1)
3699  {
3700  throw Exception("Error, TrackVectorPosition set but not EntryPos in SetTrainElementID");
3701  }
3702  if(EntryPos < 2)
3703  {
3705  }
3706  else
3707  {
3709  }
3710  }
3711  Utilities->CallLogPop(690);
3712 }
3713 
3714 // ---------------------------------------------------------------------------
3715 
3716 void TTrain::ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3717 {
3718  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ResetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3719  AnsiString(EntryPos) + "," + HeadCode);
3720  if(Track->TrackElementAt(314, TrackVectorPosition).TrackType != Bridge)
3721  {
3722  Track->TrackElementAt(315, TrackVectorPosition).TrainIDOnElement = -1;
3723  }
3724  else
3725  {
3726  if(EntryPos == -1)
3727  {
3728  throw Exception("Error, TrackVectorPosition set but not EntryPos in ResetTrainElementID");
3729  }
3730  if(EntryPos < 2)
3731  {
3732  Track->TrackElementAt(316, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 = -1;
3733  }
3734  else
3735  {
3736  Track->TrackElementAt(317, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 = -1;
3737  }
3738  if((EntryPos < 2) && (Track->TrackElementAt(318, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
3739  // i.e. other train on track 2&3
3740  {
3741  Track->TrackElementAt(319, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(320, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
3742  }
3743  else if((EntryPos > 1) && (Track->TrackElementAt(321, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
3744  // i.e. other train on track 1&2
3745  {
3746  Track->TrackElementAt(322, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(323, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
3747  }
3748  else
3749  {
3750  Track->TrackElementAt(324, TrackVectorPosition).TrainIDOnElement = -1;
3751  }
3752  }
3753  Utilities->CallLogPop(691);
3754 }
3755 
3756 // ---------------------------------------------------------------------------
3757 
3758 void TTrain::PlotAlternativeTrackRouteGraphic(int Caller, unsigned int ElementVecNum, int ElementEntryPos, int HOffset, int VOffset, TStraddle StraddleValue)
3759 {
3760  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotAlternativeTrackRouteGraphic," + AnsiString(ElementVecNum) + "," +
3761  AnsiString(ElementEntryPos) + "," + AnsiString(HOffset) + "," + AnsiString(VOffset) + "," + AnsiString(StraddleValue) + "," + HeadCode);
3762  int LockedVectorNumber;
3763 
3764  if(Track->TrackElementAt(325, ElementVecNum).TrackType != Bridge)
3765  // && (Track->TrackElementAt(326, ElementVecNum).TrackType != Crossover))
3766  {
3767  // only applies for a bridge as there can't be (or shouldn't be) 2 routes on an element that isn't a bridge
3768  Utilities->CallLogPop(692);
3769  return;
3770  }
3771  if(AllRoutes->TrackIsInARoute(0, ElementVecNum, (3 - ElementEntryPos)))
3772  // i.e other track is in a marked route
3773  // LinkPos doesn't have to be the entry position for the above check
3774  {
3775  TRect SourceRect, DestRect;
3776  DestRect.init(0, 0, 8, 8); // left, top, right, bottom
3777  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3778  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3779  // identify the route element for the other track
3780  TAllRoutes::TRouteElementPair RoutePair1, RoutePair2;
3781  RoutePair1 = AllRoutes->GetRouteElementDataFromRoute2MultiMap(13, Track->TrackElementAt(327, ElementVecNum).HLoc,
3782  Track->TrackElementAt(328, ElementVecNum).VLoc, RoutePair2);
3783  int FirstELink, SecondELink = -1;
3784  FirstELink = AllRoutes->GetFixedRouteAt(149, RoutePair1.first).GetFixedPrefDirElementAt(159, RoutePair1.second).GetELink();
3785  // must be at least one
3786  if(RoutePair2.first > -1)
3787  {
3788  SecondELink = AllRoutes->GetFixedRouteAt(150, RoutePair2.first).GetFixedPrefDirElementAt(160, RoutePair2.second).GetELink();
3789  }
3790  TPrefDirElement RouteElement;
3791  // Graphics::TBitmap *RouteGraphic;
3792  if(FirstELink == Track->TrackElementAt(329, ElementVecNum).Link[ElementEntryPos])
3793  // i.e. other track is in RoutePair2
3794  {
3795  if(SecondELink == -1)
3796  {
3797  throw Exception("Error - Second ELink should be set but isn't in PlotAlternativeTrackRouteGraphic [1]");
3798  }
3799  if(SecondELink == Track->TrackElementAt(330, ElementVecNum).Link[ElementEntryPos])
3800  // error if both have same Link number
3801  {
3802  throw Exception("Error - First & Second ELinks have same value in PlotAlternativeTrackRouteGraphic");
3803  }
3804  // RouteGraphic = AllRoutes->GetFixedRouteAt(151, RoutePair2.first).GetFixedPrefDirElementAt(161, RoutePair2.second).GetEXGraphicPtr();
3805  RouteElement = AllRoutes->GetFixedRouteAt(152, RoutePair2.first).GetFixedPrefDirElementAt(162, RoutePair2.second);
3806  }
3807  else // other track is in RoutePair1
3808  {
3809  // RouteGraphic = AllRoutes->GetFixedRouteAt(153, RoutePair1.first).GetFixedPrefDirElementAt(163, RoutePair1.second).GetEXGraphicPtr();
3810  RouteElement = AllRoutes->GetFixedRouteAt(154, RoutePair1.first).GetFixedPrefDirElementAt(164, RoutePair1.second);
3811  }
3812  Graphics::TBitmap *DestGraphic = new Graphics::TBitmap;
3813  DestGraphic->PixelFormat = pf8bit;
3814  DestGraphic->Width = 8;
3815  DestGraphic->Height = 8;
3816  DestGraphic->Transparent = true;
3817  // has to be transparent or will overwrite the track that the train has just left
3818  DestGraphic->TransparentColor = Utilities->clTransparent;
3819  DestGraphic->Canvas->CopyRect(DestRect, RouteElement.GetRouteEXGraphicPtr()->Canvas, SourceRect);
3820  Display->PlotOutput(31, (Track->TrackElementAt(331, ElementVecNum).HLoc * 16) + HOffset,
3821  (Track->TrackElementAt(332, ElementVecNum).VLoc * 16) + VOffset, DestGraphic);
3822  // plot locked route marker for other route if appropriate
3823  TPrefDirElement PrefDirElement; // holder for next call, unused
3824  // plot locked route marker if appropriate, but only when train leaves element completely as this is a 16x16 graphic
3825  if(StraddleValue == LeadMidLag)
3826  {
3828  PrefDirElement, LockedVectorNumber))
3829  {
3830  Display->PlotOutput(32, (Track->TrackElementAt(333, ElementVecNum).HLoc * 16), (Track->TrackElementAt(334, ElementVecNum).VLoc * 16),
3831  RailGraphics->LockedRouteCancelPtr[RouteElement.GetELink()]);
3832  }
3833  }
3834  delete DestGraphic;
3835  }
3836  // but - there may be a train on the other track - if so need to replot it else the section of route overwrites it
3837  // also can only be a bridge or trains either have already or soon will crash
3838  if(Track->TrackElementAt(335, ElementVecNum).TrackType != Bridge)
3839  {
3840  Utilities->CallLogPop(695);
3841  return;
3842  }
3843  if(ElementEntryPos > 1) // other train is on track 01
3844  {
3846  {
3848  }
3849  }
3850  else // other train is on track 23
3851  {
3853  {
3855  }
3856  }
3857  Utilities->CallLogPop(696);
3858 }
3859 
3860 // ---------------------------------------------------------------------------
3861 
3862 void TTrain::CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
3863 {
3864  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndCancelRouteForWrongEndEntry," + AnsiString(Element) + "," +
3865  AnsiString(EntryPos) + "," + HeadCode);
3866  int RouteNumber;
3867  bool WrongRoute = false;
3868  TPrefDirElement RouteElement;
3870  TAllRoutes::TRoute2MultiMapIterator Route2MultiMapIterator;
3871 
3872  if(AllRoutes->GetRouteTypeAndNumber(11, Element, EntryPos, RouteNumber) == TAllRoutes::NoRoute)
3873  // here if single track element & no route, or double track element with no route at EntryPos but still need to check if on points or a crossover on non-route track,
3874  // and force-erase route if so (bridge OK of course) note that GetRouteTypeAndNumber allows for points having an EntryPos of 0 or 2 & still returns correct values
3875  {
3876  if((Track->TrackElementAt(340, Element).TrackType == Crossover) || (Track->TrackElementAt(341, Element).TrackType == Points))
3877  {
3878  if(AllRoutes->GetRouteTypeAndNumber(12, Element, (3 - EntryPos), RouteNumber) != TAllRoutes::NoRoute)
3879  // (3-EntryPos) guarantees other route (0->3; 1->2; 2->1; 3->0)
3880  {
3881  if(AllRoutes->GetFixedRouteAt(179, RouteNumber).PrefDirSize() > 2)
3882  {
3883  // don't call for stub end routes
3885  }
3886  AllRoutes->GetModifiableRouteAt(13, RouteNumber).ForceCancelRoute(1);
3887  Utilities->CallLogPop(697);
3888  return;
3889  }
3890  }
3891  // also need to check for a route on a crossing diagonal
3892  TTrackElement TrackElement = Track->TrackElementAt(892, Element);
3893  int LinkNumber = TrackElement.Link[EntryPos];
3894  if((LinkNumber == 1) || (LinkNumber == 3) || (LinkNumber == 7) || (LinkNumber == 9))
3895  {
3896  if(AllRoutes->DiagonalFouledByRoute(0, TrackElement.HLoc, TrackElement.VLoc, LinkNumber))
3897  {
3898  // for LinkNumber = 1, potentially fouled diagonals are at H-1, V, Lk 3 & H, V-1, Lk 7
3899  bool LogActionErrorCalled = false;
3900  // to ensure only called once if have 2 routes on the 2 crossed diagonals
3901  if(LinkNumber == 1)
3902  {
3903  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(0, TrackElement.HLoc - 1, TrackElement.VLoc, 3, RouteNumber))
3904  {
3905  if(AllRoutes->GetFixedRouteAt(207, RouteNumber).PrefDirSize() > 2)
3906  {
3907  // don't call for stub end routes
3909  LogActionErrorCalled = true;
3910  }
3911  AllRoutes->GetModifiableRouteAt(20, RouteNumber).ForceCancelRoute(3);
3912  }
3913  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(1, TrackElement.HLoc, TrackElement.VLoc - 1, 7, RouteNumber))
3914  // not else in case have different routes on each diagonal, though shouldn't be possible
3915  {
3916  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(208, RouteNumber).PrefDirSize() > 2)
3917  {
3918  // don't call for stub end routes
3920  }
3921  AllRoutes->GetModifiableRouteAt(21, RouteNumber).ForceCancelRoute(4);
3922  }
3923  }
3924 
3925  // for LinkNumber = 3, potentially fouled diagonals are at H+1, V, Lk 1 & H, V-1 Lk 9
3926  else if(LinkNumber == 3)
3927  {
3928  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(2, TrackElement.HLoc + 1, TrackElement.VLoc, 1, RouteNumber))
3929  {
3930  if(AllRoutes->GetFixedRouteAt(209, RouteNumber).PrefDirSize() > 2)
3931  {
3932  // don't call for stub end routes
3934  LogActionErrorCalled = true;
3935  }
3936  AllRoutes->GetModifiableRouteAt(22, RouteNumber).ForceCancelRoute(5);
3937  }
3938  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(3, TrackElement.HLoc, TrackElement.VLoc - 1, 9, RouteNumber))
3939  // not else in case have different routes on each diagonal, though shouldn't be possible
3940  {
3941  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(210, RouteNumber).PrefDirSize() > 2)
3942  {
3943  // don't call for stub end routes
3945  }
3946  AllRoutes->GetModifiableRouteAt(23, RouteNumber).ForceCancelRoute(6);
3947  }
3948  }
3949 
3950  // for LinkNumber = 7, potentially fouled diagonals are at H-1, V, Lk 9 & H, V+1 Lk 1
3951  else if(LinkNumber == 7)
3952  {
3953  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(4, TrackElement.HLoc - 1, TrackElement.VLoc, 9, RouteNumber))
3954  {
3955  if(AllRoutes->GetFixedRouteAt(211, RouteNumber).PrefDirSize() > 2)
3956  {
3957  // don't call for stub end routes
3959  LogActionErrorCalled = true;
3960  }
3961  AllRoutes->GetModifiableRouteAt(24, RouteNumber).ForceCancelRoute(7);
3962  }
3963  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(5, TrackElement.HLoc, TrackElement.VLoc + 1, 1, RouteNumber))
3964  // not else in case have different routes on each diagonal, though shouldn't be possible
3965  {
3966  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(212, RouteNumber).PrefDirSize() > 2)
3967  {
3968  // don't call for stub end routes
3970  }
3971  AllRoutes->GetModifiableRouteAt(25, RouteNumber).ForceCancelRoute(8);
3972  }
3973  }
3974 
3975  // for LinkNumber = 9, potentially fouled diagonals are at H+1, V, Lk 7 & H, V+1 Lk 3
3976  else if(LinkNumber == 9)
3977  {
3978  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(6, TrackElement.HLoc + 1, TrackElement.VLoc, 7, RouteNumber))
3979  {
3980  if(AllRoutes->GetFixedRouteAt(213, RouteNumber).PrefDirSize() > 2)
3981  {
3982  // don't call for stub end routes
3984  LogActionErrorCalled = true;
3985  }
3986  AllRoutes->GetModifiableRouteAt(26, RouteNumber).ForceCancelRoute(9);
3987  }
3988  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(7, TrackElement.HLoc, TrackElement.VLoc + 1, 3, RouteNumber))
3989  // not else in case have different routes on each diagonal, though shouldn't be possible
3990  {
3991  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(214, RouteNumber).PrefDirSize() > 2)
3992  {
3993  // don't call for stub end routes
3995  }
3996  AllRoutes->GetModifiableRouteAt(27, RouteNumber).ForceCancelRoute(10);
3997  }
3998  }
3999  }
4000  }
4001  Utilities->CallLogPop(698);
4002  return; // no route on other track or no other track
4003  }
4004  // here if there is a route at Element & EntryPos - so there can't be a route on the other track if a 4 track element, unless it's a bridge & that's ok
4005  for(unsigned int x = 0; x < AllRoutes->GetFixedRouteAt(155, RouteNumber).PrefDirSize(); x++)
4006  {
4007  RouteElement = AllRoutes->GetFixedRouteAt(156, RouteNumber).GetFixedPrefDirElementAt(165, x);
4008  bool PointsAtElement = (Track->TrackElementAt(987, Element).TrackType == Points); // new at v2.4.2 for points check - Xeon repoted it 30/05/20. He found that for routes that
4009  if(RouteElement.GetTrackVectorPosition() == (unsigned int)Element) // cross bridges at both levels can have entrypos 0 & other exitpos 2 so if don't have this check can cancel a route wrongly
4010  {
4011  if(RouteElement.GetELinkPos() == EntryPos)
4012  {
4013  Utilities->CallLogPop(699);
4014  return; // right direction
4015  }
4016  else if((RouteElement.GetELinkPos() == 2) && (EntryPos == 0) && PointsAtElement)
4017  {
4018  Utilities->CallLogPop(700);
4019  return; // right direction (points)
4020  }
4021  else if((RouteElement.GetELinkPos() == 0) && (EntryPos == 2) && PointsAtElement)
4022  {
4023  Utilities->CallLogPop(701);
4024  return; // right direction (points)
4025  }
4026  else if(RouteElement.GetXLinkPos() == EntryPos)
4027  {
4028  WrongRoute = true;
4029  break; // wrong direction
4030  }
4031  else if((RouteElement.GetXLinkPos() == 2) && (EntryPos == 0) && PointsAtElement) // ok for bridges
4032  {
4033  WrongRoute = true;
4034  break; // wrong direction
4035  }
4036  else if((RouteElement.GetXLinkPos() == 0) && (EntryPos == 2) && PointsAtElement) // ok for bridges
4037  {
4038  WrongRoute = true;
4039  break; // wrong direction
4040  }
4041  }
4042  }
4043  if(!WrongRoute)
4044  {
4045  throw Exception("Error, Element in route but no route found in CheckAndCancelRouteForWrongEndEntry");
4046  }
4047  if(AllRoutes->GetFixedRouteAt(180, RouteNumber).PrefDirSize() > 2)
4048  {
4049  // don't call for stub end routes
4051  }
4052  AllRoutes->GetModifiableRouteAt(14, RouteNumber).ForceCancelRoute(2);
4053  Utilities->CallLogPop(703);
4054 }
4055 
4056 // ---------------------------------------------------------------------------
4057 
4058 void TTrain::PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
4059 {
4060  if(BackgroundColour == NewBackgroundColour)
4061  {
4062  return; // don't replot if already correct
4063 
4064  }
4065  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainWithNewBackgroundColour," + AnsiString(NewBackgroundColour));
4066  bool ColourError = false, ColourError2 = false;
4067 
4068  RailGraphics->ChangeBackgroundColour(1, FrontCodePtr, FrontCodePtr, NewBackgroundColour, BackgroundColour, ColourError);
4069  if(ColourError)
4070  {
4071  ColourError2 = true;
4072  }
4073  for(int x = 0; x < 4; x++)
4074  {
4075  RailGraphics->ChangeBackgroundColour(2, HeadCodeGrPtr[x], HeadCodeGrPtr[x], NewBackgroundColour, BackgroundColour, ColourError);
4076  if(ColourError)
4077  {
4078  ColourError2 = true;
4079  }
4080  }
4081  if(ColourError2)
4082  {
4083  TrainController->StopTTClockMessage(63, "ERROR: Colour depth insufficient to display train colours properly. Please ensure that the 'safe' (web) palette of "
4084  "256 colours can be displayed");
4085  }
4086  // NB need a separate 'for' loop since the plot order can be different from the graphic order depending on the direction
4087  // of motion
4088  for(int x = 0; x < 4; x++)
4089  {
4090  PlotTrainGraphic(6, x, Disp);
4091  }
4092  BackgroundColour = NewBackgroundColour;
4093  Display->Update();
4094  // need to keep this since Update() not called for PlotSmallOutput as too slow
4095  Utilities->CallLogPop(704);
4096 }
4097 
4098 // ---------------------------------------------------------------------------
4099 
4100 void TTrain::SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
4101 /*
4102 Note: Within the loop BrakeRate can only increase and MaxExitSpeed can only reduce
4103 
4104 Summary: Called during PlotStartPosition to set initial values, when stopped and need to restart, and during UpdateTrain when Straddle is LeadMidLag,
4105 i.e. just as the front of a train is about to move fully onto an element, where TrackVectorPosition is the element immediately in front
4106 of the element the front of the train is moving fully on to. The function calculates the times and speeds at the next half-element and
4107 full-element moves.
4108 
4109 Detail: TrackVectorPosition & EntryPos correspond to the TrackVector element immediately in front of where the train is at
4110 the end of the current Update(). EntrySpeed is needed but this is a class data member so isn't passed in. Set the
4111 train BrakeRate to zero (for now, likely to be altered later), & check if zero entry speed with another train directly in front & if so
4112 remain stopped. Pick up the half length value and speed limit for the EntryPos track, and set FrontElementLength to the length of the
4113 EntryPos track, then set LimitingSpeed to the minimum of the element speed limit or the train's maximum speed. Check if running past a
4114 red signal and set SPADFlag if so (use 1 for EntrySpeed rather than 0 as this value is a double so could be slightly in excess of 0).
4115 In this case set the brake rate to maximum to stop as soon as possible.
4116 
4117 For no SPAD calculate the distance that will be travelled at the maximum speed at which the train can exit the next element at half
4118 MaxBrakeRate, this is DistanceAtHalfBraking (also calculate DistanceAtThreeQuarterBraking - used for stopping under signaller control).
4119 DistanceAtHalfBraking is used as the limiting forward look from the next element (i.e. following EntryPos)
4120 for computing the actual braking rate. If no more restrictive speed limits or reasons to stop are found within the forward look then the
4121 train can accelerate or stay at its (local) maximum speed for the next element. The maximum speed on exit from the next element is used
4122 for calculating the forward look because it represents the worst case - i.e. assumes that the train accelerates for the next element.
4123 
4124 A loop is now entered where the CumulativeLength is updated and each successive element (if there are any - current element checked
4125 first to see whether buffers or continuation) in turn is examined: first the length of the
4126 current element is added to the cumulative length; then the half length and speedlimit are set for the next element - points are
4127 followed according to their current setting (Attribute), but derailments are ignored as these are dealt with outside this function; checks
4128 are then made to see whether the next element is a red signal (train should stop before it); next element is a buffer (train should stop
4129 at the end of it so the cumulative length has the next element length added); current element is a buffer (train should stop
4130 at the end of the current element so no need to alter the cumulative length); or have reached a named location stop position. For any of
4131 these reasons, or if stopping under signaller control, there is no more looping, instead the braking rate is calculated to bring the train
4132 to a stop over CumulativeLength. For all normal purposes the braking rate will be less than half (light braking), or less than three
4133 quarters if stopping under signaller control (heavy braking). However if signals are reset in front of a train then the train may need
4134 emergency braking (> 90% max brake rate) and a SPAD may result. Similarly if points are chaged in front of a train that divert it into a
4135 siding then again emeregency braking may be necessary and a crash may result.
4136 
4137 If the train is due to stop then the function calculates the half and full times and speeds and returns. However the calculation depends
4138 on the conditions at entry. If the EntrySpeed is lower than MaxHalfSpeed and the EntryPos element is the one
4139 that the train has to stop at the end of, as it might be for example if train had been stopped at a signal and the next element is a
4140 buffer, then the train accelerates for half the element and brakes for the other half.
4141 Now the BrakeRate is calculated (limited to the MaxBrakeRate), but if it is less than a value calculated at an earlier pass round
4142 the loop then it retains its earlier value (may be due to a close speed restriction that requires more braking than a more distant stop
4143 requirement). The MaxExitSpeedAtHalfBraking (maximum speed at which the train can leave the current element and still stop when required
4144 at half the max braking rate) value is also calculated using EntrySpeed and CumulativeLength, but limiting it to the line speed limit or
4145 train MaxRunningSpeed whichever is the lower. If EntrySpeed > MaxExitSpeedAtHalfBraking then braking is required, so the half and full
4146 speed and time values for the current element are calculated using BrakeRate, EntrySpeed and CurrentElementHalfLength. If need to stop
4147 at the end of the current elemecumulativent for other than a red signal (SPADs can occur) then ExitSpeedFull is set to 0. It should be calculated
4148 as 0 anyway for other than a red signal but this makes sure. If EntrySpeed <= MaxExitSpeedAtHalfBraking then can calculate the half and
4149 full speed and time values for acceleration over the current element, but limit ExitSpeedHalf & Full to MaxExitSpeedAtHalfBraking or to
4150 the current element speed limit if necessary. Check whether ExitSpeedHalf <= EntrySpeed (+0.01 since it's a double) and use constant speed
4151 time values for Half & Full if so, but prior to this increase EntrySpeed if necessary to avoid a divide by zero error.
4152 
4153 If the train is not due to stop within the DistanceAtHalfBraking from the next element following EntryPos then the next element (if there
4154 is one) is checked to see if its speed limit is less than the current value of LimitingSpeed (which is the minimum of any earlier element's
4155 speed limit that has been examined within the loop and the train's MaxRunning speed), and if so LimitingSpeed is set down to it. Now
4156 the MaxExitSpeedAtHalfBraking is calculated, limiting it to LimitingSpeed if less, in case need to accelerate in the current element, in
4157 which case the exit speeds need to be limited to MaxExitSpeedAtHalfBraking. If EntrySpeed > LimitingSpeed then calculate the braking rate
4158 to bring the speed down to LimitingSpeed in CumulativeLength, keeping the existing BrakeRate value if lower and keeping it within
4159 MaxBrakeRate.
4160 
4161 Then, providing the current element isn't a buffer or continuation, the 'Current' values are updated from the 'Next' values ready for
4162 the next loop iteration. The loop is broken out of if the current element is a buffer or continuation, the next element is a
4163 continuation, or (CumulativeLength - FrontElementLength) >= DistanceAtHalfBraking.
4164 
4165 Now the final Half and Full values can be set for braking (if BrakeRate > 0.01), or accelerating - limiting the half and full exit speed
4166 values to MaxExitSpeedAtHalfBraking if necessary, and using constant speed time values if the exit speeds aren't much different to
4167 EntrySpeed and EntrySpeed > 0.01 (to avoid a divide by zero error).
4168 
4169 Note that in no circumstances will a train stop when straddling 3 elements, it will always be fully on two elements. This is ensured
4170 by UpdateTrain() which never sets any stop conditions unless the train is fully on 2 elements when that function returns, i.e. entered
4171 when Straddle == LeadMidLag
4172 */
4173 {
4174  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainMovementValues," + AnsiString(TrackVectorPosition) + "," +
4175  AnsiString(EntryPos) + "," + HeadCode);
4176  int EntryHalfLength, CurrentElementHalfLength, NextElementHalfLength, CumulativeLength = 0, CurrentTrackVectorPosition = TrackVectorPosition;
4177  int DistanceAtHalfBraking, DistanceAtThreeQuarterBraking, ExitPos, NextTrackVectorPosition, NextEntryPos;
4178  bool RedSignalFlag = false, BuffersFlag = false, StationFlag = false, BuffersOrContinuationNowFlag = false, ContinuationNextFlag = false,
4179  TrainInFrontInSignallerModeFlag = false;
4180  double LimitingSpeed, FrontElementMaxSpeed, MaxExitSpeedAtHalfBrakingSquared, MaxExitSpeedAtHalfBraking, NextSpeedLimit, TempBrakeRate;
4181  double ExitSpeedHalfSquared, ExitSpeedFullSquared;
4182  bool SignallerStopRequired = false;
4183 
4185  // set high to begin with to avoid divide by zero errors on restart after stops, will be set lower later
4186 
4187  // Member variables: EntrySpeed, ExitSpeedHalf, ExitSpeedFull, MaxExitSpeed, BrakeRate, EntryTime, ExitTimeHalf, ExitTimeFull, FrontElementSpeedLimit, FrontElementLength;
4188 
4189  OneLengthAccelDecel = false;
4190  BrakeRate = 0;
4191  if(PowerAtRail < 1)
4192  {
4193  BrakeRate = CoastingBrakeRate; //brings train to a stop in 13km in 15min from starting speed of 100km/h (from research)
4194  }
4195 //find FrontElementLength & FrontElementSpeedLimit (these correspond to TrackVectorPosition input value);
4196  if(CurrentTrackVectorPosition > -1)
4197  {
4198  if(Track->TrackElementAt(855, CurrentTrackVectorPosition).TrackType == Points) // this test & section added at v0.6
4199  {
4200  if((EntryPos == 0) || (EntryPos == 2))
4201  {
4202  if(Track->TrackElementAt(856, CurrentTrackVectorPosition).Attribute == 0)
4203  {
4204  CurrentElementHalfLength = (Track->TrackElementAt(857, CurrentTrackVectorPosition).Length01) / 2;
4205  FrontElementSpeedLimit = Track->TrackElementAt(858, CurrentTrackVectorPosition).SpeedLimit01;
4206  }
4207  else
4208  {
4209  CurrentElementHalfLength = (Track->TrackElementAt(859, CurrentTrackVectorPosition).Length23) / 2;
4210  FrontElementSpeedLimit = Track->TrackElementAt(860, CurrentTrackVectorPosition).SpeedLimit23;
4211  }
4212  }
4213  else if(EntryPos == 1)
4214  {
4215  CurrentElementHalfLength = (Track->TrackElementAt(861, CurrentTrackVectorPosition).Length01) / 2;
4216  FrontElementSpeedLimit = Track->TrackElementAt(862, CurrentTrackVectorPosition).SpeedLimit01;
4217  }
4218  else // == 3
4219  {
4220  CurrentElementHalfLength = (Track->TrackElementAt(863, CurrentTrackVectorPosition).Length23) / 2;
4221  FrontElementSpeedLimit = Track->TrackElementAt(864, CurrentTrackVectorPosition).SpeedLimit23;
4222  }
4223  }
4224  else
4225  {
4226  if(EntryPos > 1)
4227  {
4228  CurrentElementHalfLength = (Track->TrackElementAt(348, CurrentTrackVectorPosition).Length23) / 2;
4229  FrontElementSpeedLimit = Track->TrackElementAt(349, CurrentTrackVectorPosition).SpeedLimit23;
4230  }
4231  else
4232  {
4233  CurrentElementHalfLength = (Track->TrackElementAt(350, CurrentTrackVectorPosition).Length01) / 2;
4234  FrontElementSpeedLimit = Track->TrackElementAt(351, CurrentTrackVectorPosition).SpeedLimit01;
4235  }
4236  }
4237  EntryHalfLength = CurrentElementHalfLength;
4238  FrontElementLength = 2 * CurrentElementHalfLength;
4239  }
4240  else
4241  {
4242  throw Exception("Error - CurrentTrackVectorPosition < 0 in SetTrainMovementValues");
4243  }
4244  if((CurrentElementHalfLength < 0) || (FrontElementSpeedLimit < 0))
4245  {
4246  throw Exception("Error - HalfLength or SpeedLimit < 0 in SetTrainMovementValues");
4247  }
4248  // check if zero entry speed with another train directly in front & if so remain stopped
4249  if((EntryPos > -1) && Track->OtherTrainOnTrack(2, CurrentTrackVectorPosition, EntryPos, TrainID) && (EntrySpeed < 1))
4250  {
4251  EntrySpeed = 0;
4252  ExitSpeedHalf = 0;
4253  ExitSpeedFull = 0;
4254  MaxExitSpeed = 0;
4255  BrakeRate = 0;
4256  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4257  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4258  StoppedForTrainInFront = true;
4259  TrainInFront = true;
4260  Utilities->CallLogPop(705);
4261  return;
4262  }
4263  // new at v2.4.0 - check for stopped and zero power
4264  if((EntrySpeed < 1) && PowerAtRail < 1)
4265  {
4266  EntrySpeed = 0;
4267  ExitSpeedHalf = 0;
4268  ExitSpeedFull = 0;
4269  MaxExitSpeed = 0;
4270  BrakeRate = 0;
4271  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4272  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4273  StoppedWithoutPower = true;
4274  Utilities->CallLogPop(2125);
4275  return;
4276  }
4277 //set LimitingSpeed & FrontElementMaxSpeed (internal values)
4278  if(BeingCalledOn)
4279  {
4280  LimitingSpeed = CallOnMaxSpeed;
4281  }
4282  else
4283  {
4284  LimitingSpeed = MaximumSpeedLimit;
4285  }
4286  if(LimitingSpeed > FrontElementSpeedLimit)
4287  {
4288  LimitingSpeed = FrontElementSpeedLimit;
4289  }
4290  if(LimitingSpeed > MaxRunningSpeed) //MaxRunningSpeed is set in AddTrain depending on timetable or signaller control mode
4291  {
4292  LimitingSpeed = MaxRunningSpeed;
4293  }
4294  FrontElementMaxSpeed = LimitingSpeed;
4295 
4296 /*
4297  for braking the deceleration rate is constant so the following formuli (Newton's Laws) are used:-
4298  (1) V^2/(3.6^2) = U^2/(3.6^2) - 2FS;
4299  (2) V/3.6 = U/3.6 - FT;
4300  (3) S = UT/3.6 - 0.5FT^2
4301  where(V = final speed in kph [km/h/3.6 = m/s], U = initial speed in km/h, F = deceleration rate in m/s/s, S = distance in m & T = time in secs)
4302 
4303  for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4304  (4) V^2/(3.6^2) - U^2/(3.6^2) = A^2T;
4305  (5) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4306  where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time in secs
4307  It's a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4308 
4309  calc max speed that can attain on exit from next element (as could accelerate over next element) and use that speed to calc
4310  DistanceAtHalfBraking, if use actual speed may miss a stop requirement just outside look-ahead & accelerate, and at next calc
4311  be unable to stop or have hard acceleration followed immediately by hard braking, this speed makes for smoother operation
4312 */
4313 
4314 // check if running past a red signal without permission
4315  if((Track->TrackElementAt(352, CurrentTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(EntryPos)] == Signal) &&
4316  (Track->TrackElementAt(353, CurrentTrackVectorPosition).Attribute == 0) && (EntrySpeed > 1) && !AllowedToPassRedSignal &&
4317  !Track->TrackElementAt(1553, CurrentTrackVectorPosition).CallingOnSet)
4318  { //CallingOnSet added at v2.14.0
4319  SPADFlag = true; // user has to intervene to reset & restart after spad
4320  }
4321  if(!SPADFlag)
4322  {
4323  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4324  // to begin with calc the maximum exit speed (assumes accelerating) and then reduce it if necessary
4325  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4326  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4327  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4328  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4329 
4330  double ExitSpeedAtMaxBraking;
4331  // below introduced at v2.4.0, was ExitSpeedFull = LimitingSpeed; but that allowed very high brake rates when
4332  // took signaller control of a fast failed train with signaller limiting speed 30km/h
4334  {
4335  ExitSpeedAtMaxBraking = 0;
4336  }
4337  else
4338  {
4339  ExitSpeedAtMaxBraking = sqrt((EntrySpeed * EntrySpeed) - 2 * MaxBrakeRate * FrontElementLength);
4340  }
4341  double SpeedToUse;
4342  // use the highest of LimitingSpeed or ExitSpeedAtMaxBraking - added at v2.4.2 because trains entering at a continuation with zero (or very low) speed
4343  // & 2 elements before signal caused ExitSpeedAtMaxBraking & hence DistanceAtHalfBraking and DistanceAtThreeQuarterBraking to be zero, so no restriction was recognised
4344  // for first element & train accelerated at maximum rate, then at 2nd element train couldn't brake in time and overran the signal - notified by Micke via Discord on 02/06/20
4345  if(ExitSpeedAtMaxBraking > LimitingSpeed)
4346  {
4347  SpeedToUse = ExitSpeedAtMaxBraking;
4348  }
4349  else
4350  {
4351  SpeedToUse = LimitingSpeed;
4352  }
4353  if(ExitSpeedFull > SpeedToUse)
4354  {
4355  ExitSpeedFull = SpeedToUse;
4356  }
4357  DistanceAtHalfBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / MaxBrakeRate;
4358  DistanceAtThreeQuarterBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / 1.5 / MaxBrakeRate; // used for signaller stops
4359 
4360  //now enter a do loop to examine each element in turn from the front of the train to calc the cumulative length and to see if a stop is required (flag set if so -
4361  //RedSignalFlag, BuffersFlag, StationFlag, TrainInFrontInSignallerModeFlag, SignallerStopRequired, StepForwardFlag) in which case there are no more loops
4362  // break out of the loop when ((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking ) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) || SignallerStoppingFlag);
4363 
4364  do
4365  {
4366  RedSignalFlag = false;
4367  BuffersFlag = false;
4368  StationFlag = false;
4369  BuffersOrContinuationNowFlag = false;
4370  ContinuationNextFlag = false;
4371  // have to reset this after the above test
4372  // add current element length to CumulativeLength
4373  CumulativeLength += (2 * CurrentElementHalfLength);
4374  if((CumulativeLength >= DistanceAtThreeQuarterBraking) && (TrainMode == Signaller) && SignallerStoppingFlag)
4375  {
4376  SignallerStopRequired = true;
4377  // once set stays set until SignallerStoppingFlag reset, providing !BuffersOrContinuationNowFlag,
4378  // set SignallerStopBrakeRate to stop in CumulativeLength unless already higher (i.e. can only increase)
4379  double TempBR = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4380  if(SignallerStopBrakeRate < TempBR)
4381  {
4382  SignallerStopBrakeRate = TempBR;
4383  }
4384  }
4385  // first check for stops within the length of the current element, where don't want any more checks & don't want
4386  // to add in any extra to the CumulativeLength. Only applies for buffers & station stops as signals should have been caught
4387  // during the last loop when the NextTrackVectorPosition was the signal.
4388 
4389  // check if current element is a buffer
4390  if(Track->TrackElementAt(374, CurrentTrackVectorPosition).TrackType == Buffers)
4391  {
4392  // no need to add in the length of this element to CumulativeLength as already included
4393  BuffersFlag = true;
4394  }
4395  // check if current element is a station stop
4396  if(TrainMode == Timetable)
4397  {
4398  bool StopRequired = false;
4399  if(!TimetableFinished && (NameInTimetableBeforeCDT(12, Track->TrackElementAt(375, CurrentTrackVectorPosition).ActiveTrackElementName,
4400  StopRequired) > -1) && ((Track->TrackElementAt(376, CurrentTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) ||
4401  (Track->TrackElementAt(377, CurrentTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) ||
4402  (Track->TrackElementAt(1642, CurrentTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) ||
4403  (Track->TrackElementAt(1643, CurrentTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4404  {
4405  // no need to add in the length of element to CumulativeLength
4406  if(StopRequired)
4407  {
4408  StationFlag = true;
4409  }
4410  }
4411  }
4412  else
4413  {
4414  StationFlag = false;
4415  }
4416  // set NextHalfLength & NextSpeedLimit, but only if current element not buffers or exit continuation - no next element for them
4417  if(((Track->TrackElementAt(354, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(355,
4418  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4419  {
4420  BuffersOrContinuationNowFlag = true;
4421  }
4422  if(!BuffersOrContinuationNowFlag && !BuffersFlag && !StationFlag) // skip if buffers or station flags already set
4423  {
4424  if(Track->TrackElementAt(356, CurrentTrackVectorPosition).TrackType == Points)
4425  {
4426  if((EntryPos == 0) || (EntryPos == 2))
4427  {
4428  if(Track->TrackElementAt(357, CurrentTrackVectorPosition).Attribute == 0)
4429  {
4430  ExitPos = 1;
4431  }
4432  else
4433  {
4434  ExitPos = 3;
4435  }
4436  }
4437  else
4438  {
4439  ExitPos = 0;
4440  }
4441  }
4442  else
4443  {
4444  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4445  }
4446  NextTrackVectorPosition = Track->TrackElementAt(358, CurrentTrackVectorPosition).Conn[ExitPos];
4447  NextEntryPos = Track->TrackElementAt(359, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4448  if(NextTrackVectorPosition > -1)
4449  {
4450  if(Track->TrackElementAt(845, NextTrackVectorPosition).TrackType == Points)
4451  // this test & section added at v0.6
4452  {
4453  if((NextEntryPos == 0) || (NextEntryPos == 2))
4454  {
4455  if(Track->TrackElementAt(846, NextTrackVectorPosition).Attribute == 0)
4456  {
4457  NextElementHalfLength = (Track->TrackElementAt(847, NextTrackVectorPosition).Length01) / 2;
4458  NextSpeedLimit = Track->TrackElementAt(848, NextTrackVectorPosition).SpeedLimit01;
4459  }
4460  else
4461  {
4462  NextElementHalfLength = (Track->TrackElementAt(849, NextTrackVectorPosition).Length23) / 2;
4463  NextSpeedLimit = Track->TrackElementAt(850, NextTrackVectorPosition).SpeedLimit23;
4464  }
4465  }
4466  else if(NextEntryPos == 1)
4467  {
4468  NextElementHalfLength = (Track->TrackElementAt(851, NextTrackVectorPosition).Length01) / 2;
4469  NextSpeedLimit = Track->TrackElementAt(852, NextTrackVectorPosition).SpeedLimit01;
4470  }
4471  else // == 3
4472  {
4473  NextElementHalfLength = (Track->TrackElementAt(853, NextTrackVectorPosition).Length23) / 2;
4474  NextSpeedLimit = Track->TrackElementAt(854, NextTrackVectorPosition).SpeedLimit23;
4475  }
4476  }
4477  else
4478  {
4479  if(NextEntryPos > 1)
4480  {
4481  NextElementHalfLength = (Track->TrackElementAt(360, NextTrackVectorPosition).Length23) / 2;
4482  NextSpeedLimit = Track->TrackElementAt(361, NextTrackVectorPosition).SpeedLimit23;
4483  }
4484  else
4485  {
4486  NextElementHalfLength = (Track->TrackElementAt(362, NextTrackVectorPosition).Length01) / 2;
4487  NextSpeedLimit = Track->TrackElementAt(363, NextTrackVectorPosition).SpeedLimit01;
4488  }
4489  }
4490  }
4491  else
4492  {
4493  throw Exception("Error - Trying to access NextTrackVectorPosition when none present in SetTrainMovementValues");
4494  }
4495  // now check for stops, first cover those where don't want to add in length of next element
4496  // check if next element is a red signal - Attr 0,
4497  // note that this doesn't apply to trains stopped at a red signal since the signal position is
4498  // CurrentTrackVectorPosition not NextTrackVectorPosition
4499  bool StopRequired;
4500  if(Track->TrackElementAt(364, NextTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal)
4501  {
4502  if(Track->TrackElementAt(365, NextTrackVectorPosition).Attribute == 0)
4503  {
4504  // no need to add in the length of element to CumulativeLength
4505  RedSignalFlag = true;
4506  }
4507  // next element is a red signal
4508  }
4509  // check if current element is a station & next element contains a train - trains will always stop without crashing at a
4510  // station they are due to stop at even if there is a train in front blocking the normal stop position - providing there is
4511  // at least one platform element free
4513  CurrentTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && Track->OtherTrainOnTrack(3, NextTrackVectorPosition, NextEntryPos, TrainID))
4514  {
4515  // no need to add in the length of element to CumulativeLength
4516  if(StopRequired)
4517  {
4518  StationFlag = true;
4519  }
4520  }
4521  // check if next element contains a train & in Signaller mode (always stops for train in front if in signaller mode)
4522  else if((TrainMode == Signaller) && Track->OtherTrainOnTrack(4, NextTrackVectorPosition, NextEntryPos, TrainID))
4523  // (Track->TrackElementAt(651, NextTrackVectorPosition).TrainIDOnElement > -1))
4524  {
4525  // no need to add in the length of element to CumulativeLength
4526  TrainInFrontInSignallerModeFlag = true;
4527  }
4528  // check if next element is a buffer, but if StepForwardFlag true then need to stop before reach the buffers
4529  else if((Track->TrackElementAt(366, NextTrackVectorPosition).TrackType == Buffers) && !StepForwardFlag)
4530  {
4531  // need to add in the length of that element to CumulativeLength
4532  CumulativeLength += Track->TrackElementAt(367, NextTrackVectorPosition).Length01;
4533  BuffersFlag = true;
4534  }
4535  // check if next element is a station stop
4537  NextTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && ((Track->TrackElementAt(371,
4538  NextTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) || (Track->TrackElementAt(372,
4539  NextTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) || (Track->TrackElementAt(1644,
4540  NextTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) || (Track->TrackElementAt(1645,
4541  NextTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4542  { // need to add in the length of that element to CumulativeLength if a stop required
4543  if(StopRequired)
4544  {
4545  StationFlag = true;
4546  CumulativeLength += Track->TrackElementAt(373, NextTrackVectorPosition).Length01;
4547  }
4548  }
4549  }
4550  //now can decide whether need to stop over CumulativeLength
4551  if(RedSignalFlag || BuffersFlag || StationFlag || TrainInFrontInSignallerModeFlag || SignallerStopRequired || StepForwardFlag) // no more loops
4552  {
4553  // have to come to a stop over CumulativeLength
4554  if(CumulativeLength == FrontElementLength)
4555  // will be if StepForwardFlag (if stopped to begin with on zero power then earlier check will intercept it and it won't reach here
4556  // only one length to go before stop so check whether need to accelerate for half length then brake for latter
4557  // half; calc speed at halfway point that corresponds to half braking rate for latter half of track element,
4558  // and if less than EntrySpeed then skip this section (don't need any acceleration)
4559  // if not calc speed at halfway point & if less than above set half speed to this value;
4560  // use constant acceleration in calculating half time point
4561  {
4562  MaxExitSpeed = 0;
4563  double MaxHalfSpeed;
4564  double MaxHalfSpeedAtHalfBraking = 3.6 * sqrt(MaxBrakeRate * FrontElementLength / 2);
4565  // have to halve the element length, & can't be zero or negative so no need to test
4566  // if(MaxHalfSpeedAtHalfBraking > LimitingSpeed) MaxHalfSpeed = LimitingSpeed; else MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4567  if(MaxHalfSpeedAtHalfBraking > FrontElementMaxSpeed)
4568  {
4569  MaxHalfSpeed = FrontElementMaxSpeed;
4570  }
4571  else
4572  {
4573  MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4574  }
4575  if(MaxHalfSpeed > (2 * EntrySpeed) && (PowerAtRail > 1)) //PowerAtRail condition added at v2.18.0
4576  // use 2x to prevent kangarooing at last element when had
4577  // been braking smoothly at less that 50% braking rate, 2x should prevent all but extreme cases
4578  {
4579  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4580  0.333334);
4581  bool HalfSpeedLimited = false;
4582  if(MaxHalfSpeed < ExitSpeedHalf)
4583  {
4584  ExitSpeedHalf = MaxHalfSpeed;
4585  HalfSpeedLimited = true;
4586  }
4587  if(PowerAtRail > 1)
4588  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4589  {
4590  // [km/h/3.6 = m/s]
4591  ExitTimeHalf =
4592  EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4593  }
4594  else
4595  {
4596  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4597  }
4598  // the above is the time taken to accelerate to ExitSpeedHalf, so if this is reached before the half
4599  // way point (i.e. HalfSpeedLimited is set) then the time will be too short; change later to equal the
4600  // braking time; not fully accurate but better to be equal than have a short acceleration period followed
4601  // by a long braking period
4602  ExitSpeedFull = 0;
4603  TempBrakeRate = ExitSpeedHalf * ExitSpeedHalf / 2 / 3.6 / 3.6 / EntryHalfLength;
4604  if(TempBrakeRate > MaxBrakeRate)
4605  {
4606  TempBrakeRate = MaxBrakeRate;
4607  }
4608  // shouldn't be but leave in anyway
4609  if(TempBrakeRate > BrakeRate)
4610  {
4611  BrakeRate = TempBrakeRate;
4612  }
4613  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4614  ExitTimeFull = ExitTimeHalf + TDateTime(ExitSpeedHalf / 3.6 / BrakeRate / 86400.0);
4615  if(HalfSpeedLimited)
4616  // this is the change referred to above
4617  {
4618  TDateTime BrakingTime = ExitTimeFull - ExitTimeHalf;
4619  ExitTimeHalf = EntryTime + BrakingTime;
4620  ExitTimeFull = ExitTimeHalf + BrakingTime;
4621  }
4622  OneLengthAccelDecel = true; //used in TrackTrainFloat in InterfaceUnit.cpp to show accelerating for first half move then decelerating
4623  Utilities->CallLogPop(1095);
4624  return;
4625  }
4626  }
4627  // set braking to achieve speed = 0 @ CumulativeLength up to MaxBrakeRate
4628  // calc MaxExitSpeed for element at EntryPosition & set to this or existing val if lower,
4629  // calc th, tf, sh, & sf
4630  TempBrakeRate = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4631  if(TempBrakeRate > MaxBrakeRate)
4632  {
4633  TempBrakeRate = MaxBrakeRate;
4634  }
4635  if(TempBrakeRate > BrakeRate)
4636  {
4637  BrakeRate = TempBrakeRate;
4638  }
4639  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4640  if(SignallerStopRequired)
4641  // set BrakeRate to max of its calculated value or SignallerStopBrakeRate
4642  {
4644  {
4646  // this prevents the brakerate from reducing for a signaller stop
4647  // regardless of other conditions that may change as progress round the loop
4648  }
4649  }
4651  // prevents BrakeRate dropping below SignallerStopBrakeRate once it's been set whether or not SignallerStopRequired set
4652  // SignallerStopRequired may not be set if a red signal found in a later calc, & brakerate may then drop
4653  {
4655  }
4656  int TempMaxExitSpeed;
4657  // calc current value & if less than MaxExitSpeed set that to this
4658  MaxExitSpeedAtHalfBrakingSquared = 3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength);
4659  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4660  {
4661  MaxExitSpeedAtHalfBraking = 0;
4662  }
4663  else
4664  {
4665  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4666  }
4667  // if(MaxExitSpeedAtHalfBraking > LimitingSpeed) MaxExitSpeed = LimitingSpeed; else MaxExitSpeed = MaxExitSpeedAtHalfBraking;
4668  // I think the above was dropped because it could cause MaxExitSpeed to increase (MaxExitSpeed is an external variable retained between loops)
4669  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4670  {
4671  TempMaxExitSpeed = FrontElementMaxSpeed;
4672  }
4673  else
4674  {
4675  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4676  }
4677  if(TempMaxExitSpeed < MaxExitSpeed)
4678  {
4679  MaxExitSpeed = TempMaxExitSpeed;
4680  }
4681  // here have EntrySpeed & MaxExitSpeed (for the next element), BrakeRate (to bring speed to zero over
4682  // Cumulativelength, and Cumulativelength
4683 
4684  if((EntrySpeed > MaxExitSpeed) || SignallerStopRequired || (SignallerStopBrakeRate > 0.01)) // need to brake
4685  {
4686  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4687  if(ExitSpeedHalfSquared < 10)
4688  {
4689  ExitSpeedHalf = 0;
4690  }
4691  else
4692  {
4693  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4694  }
4695  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4696  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4697  if(ExitSpeedFullSquared < 10)
4698  {
4699  ExitSpeedFull = 0;
4700  }
4701  else
4702  {
4703  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4704  }
4705  if((StationFlag) && (CumulativeLength == FrontElementLength))
4706  {
4707  ExitSpeedFull = 0;
4708  // force a stop for station (not for buffers or red signal)
4709  }
4710  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4711  }
4712  // new condition at v2.4.0
4713  else if(PowerAtRail < 1)
4714  // use EntrySpeed, CumulativeLength & BrakeRate to calculate the half and full exit times and speeds for next element
4715  // avoid using AValue in denominator or have excessively long times
4716  {
4717  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * BrakeRate * FrontElementLength);
4718  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4719  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / (3.6 * BrakeRate * 86400.0));
4720 
4721  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * FrontElementLength);
4722  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4723  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / (3.6 * BrakeRate * 86400.0));
4724  }
4725  else // e.g. moving towards a signal or station after a speed limit, so can accelerate unless no power
4726  // without the power need above condition or have hours of delay times, above added at v2.4.0
4727  {
4728  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4729  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4730  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4731  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4732  BrakeRate = 0;
4733  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4734  0.333334);
4735  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4736  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4738  // can accelerate continually over the half length
4739  {
4740  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4741  / 86400.0);
4743  // can accelerate continually over the full length
4744  // i.e both (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4745  {
4746  ExitTimeFull =
4747  EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4748  }
4749  else // (ExitSpeedHalf <= MaxExitSpeed) but (ExitSpeedFull > MaxExitSpeed)
4750  // accelerate to MaxExitSpeed then reamin at this speed for rest of element
4751  {
4752  // added at v0.6 as a safeguard
4753  if(MaxExitSpeed < EntrySpeed)
4754  {
4756  }
4757  // to prevent DeltaExitTimeToMaxInSecs being negative
4758  if(MaxExitSpeed < 1)
4759  {
4760  MaxExitSpeed = 1;
4761  }
4762  // to prevent divide by zero error
4763  // below as was
4765  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4766  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4767  (1.5 * AValue * AValue);
4768  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4769  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4770  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4771  }
4772  }
4773  else // ExitSpeedHalf > MaxExitSpeed, so ExitSpeedFull must also be > MaxExitSpeed
4774  // accelerate over first half to MaxExitSpeed then remain at this speed for rest of the first and
4775  // second halves of the element
4776  {
4777  // added at v0.6 as a safeguard
4778  if(MaxExitSpeed < EntrySpeed)
4779  {
4781  }
4782  // to prevent DeltaExitTimeToMaxInSecs being negative
4783  if(MaxExitSpeed < 1)
4784  {
4785  MaxExitSpeed = 1; // to prevent divide by zero error
4786  }
4787  // below as was
4789  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4790  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4791  (1.5 * AValue * AValue);
4792  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax;
4793  // remaining distance to half length
4794  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4795  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4797  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4798  }
4799  }
4800  Utilities->CallLogPop(706);
4801  return;
4802  }
4803  else
4804  {
4805  if(!BuffersOrContinuationNowFlag)
4806  {
4807  if(NextSpeedLimit < LimitingSpeed)
4808  {
4809  LimitingSpeed = NextSpeedLimit;
4810  }
4811  }
4812  // calc max exit speed at half braking to ensure don't accelerate past it (if acceleration is required)
4813  int TempMaxExitSpeed;
4814  // calc current value & if less than MaxExitSpeed set that to this
4815  // Note that LimitingSpeed is the max value at the end of CumulativeLength, so MaxExitSpeedAtHalfBrakingSquared will be larger
4816  MaxExitSpeedAtHalfBrakingSquared = (LimitingSpeed * LimitingSpeed) + (3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength));
4817  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4818  {
4819  MaxExitSpeedAtHalfBraking = 0;
4820  }
4821  else
4822  {
4823  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4824  }
4825  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4826  {
4827  TempMaxExitSpeed = FrontElementMaxSpeed;
4828  }
4829  else
4830  {
4831  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4832  }
4833  if(TempMaxExitSpeed < MaxExitSpeed)
4834  {
4835  MaxExitSpeed = TempMaxExitSpeed;
4836  }
4837  // MaxExitSpeed is an external variable & this can reduce its value
4838  if(EntrySpeed > LimitingSpeed)
4839  // note that LimitingSpeed is more restrictive than MaxExitSpeed
4840  // calc TempBrakeRate & set BrakeRate to this or keep existing val if higher
4841  {
4842  TempBrakeRate = ((EntrySpeed * EntrySpeed) - (LimitingSpeed * LimitingSpeed)) / 3.6 / 3.6 / 2 / CumulativeLength;
4843  if(TempBrakeRate > MaxBrakeRate)
4844  {
4845  TempBrakeRate = MaxBrakeRate;
4846  }
4847  // shouldn't be for speedlimits since all known in advance, but include anyway
4848  if(TempBrakeRate > BrakeRate)
4849  {
4850  BrakeRate = TempBrakeRate;
4851  }
4852  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4853  }
4854  }
4855  if(!BuffersOrContinuationNowFlag)
4856  {
4857  CurrentTrackVectorPosition = NextTrackVectorPosition;
4858  EntryPos = NextEntryPos;
4859  CurrentElementHalfLength = NextElementHalfLength;
4860  if(Track->TrackElementAt(378, NextTrackVectorPosition).TrackType == Continuation)
4861  {
4862  ContinuationNextFlag = true;
4863  }
4864  }
4865  }
4866  while(((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) ||
4868  // check from the end of the front element, if include the front element and could brake during it, then will skip further loops
4869  // & miss a stop requirement just beyond the front element. happened in Richard Standing's railway where a new service introduced
4870  // on a 100m length, with 20m length after & then a red signal - train accelerated over the 100m then caused a SPAD as too short a
4871  // stopping distance after it.
4872 
4873  //(!BuffersOrContinuationNowFlag && !ContinuationNextFlag) true when no continuation on either the next element and next but one element.
4874  //There won't be a buffer on the next element or would have caught earlier, just using this flag for convenience.
4875 
4876  // If SignallerStoppingFlag then don't exit loop because of an imminent continuation, because continuation
4877  // not immediately in front (if it is then LeadElement will be the continuation & SignallerStoppingFlag will be reset in UpdateTrain()),
4878  // need to at least give a chance to stop on signaller command, if keep moving until continuation is immediately in front then will
4879  // exit loop & that is OK as don't want to stop so close to a continuation, if that happens it means that the command to stop was given
4880  // too late
4881 
4882  // set final braking or acc'n speed & time values
4883  if(BrakeRate > 0.01)
4884  {
4885  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4886  if(ExitSpeedHalfSquared < 10)
4887  {
4888  ExitSpeedHalf = 0;
4889  }
4890  else
4891  {
4892  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4893  }
4894  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4895  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4896  if(ExitSpeedFullSquared < 10)
4897  {
4898  ExitSpeedFull = 0;
4899  }
4900  else
4901  {
4902  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4903  }
4904  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4905  }
4906  else
4907  {
4908  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4909  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4910  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time
4911  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4912 
4913  BrakeRate = 0;
4914  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4915  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4916  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4918  {
4919  if(PowerAtRail > 1)
4920  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4921  {
4922  // [km/h/3.6 = m/s]
4923  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4924  / 86400.0);
4925  }
4926  else
4927  {
4928  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4929  }
4931  // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4932  {
4933  if(PowerAtRail > 1)
4934  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4935  {
4936  // [km/h/3.6 = m/s]
4937  ExitTimeFull = EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4938  / 86400.0);
4939  }
4940  else
4941  {
4942  ExitTimeFull = EntryTime + TDateTime(2 * EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4943  }
4944  }
4945  else // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull > MaxExitSpeed)
4946  {
4947  // added at v0.6 as a safeguard
4948  if(MaxExitSpeed < EntrySpeed)
4949  {
4951  }
4952  // to prevent DeltaExitTimeToMaxInSecs being negative
4953  if(MaxExitSpeed < 1)
4954  {
4955  MaxExitSpeed = 1; // to prevent divide by zero error
4956  }
4957  // below as was
4959  double DeltaExitTimeToMaxInSecs;
4960  double DistanceToMax;
4961  if(PowerAtRail > 1) // added at v2.4.0
4962  {
4963  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4964  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4965  (1.5 * AValue * AValue);
4966  }
4967  else // shouldn't ever get here given that ExitSpeedFull > ExitSpeedHalf
4968  {
4969  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4970  // these not really accurate but will be good enough
4971  DistanceToMax = EntryHalfLength;
4972  }
4973  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4974  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4975  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4976  }
4977  }
4978  else // ExitSpeedHalf > MaxExitSpeed, ExitSpeedFull must be > MaxExitSpeed
4979  {
4980  // added at v0.6 as a safeguard
4981  if(MaxExitSpeed < EntrySpeed)
4982  {
4984  }
4985  // to prevent DeltaExitTimeToMaxInSecs being negative
4986  if(MaxExitSpeed < 1)
4987  {
4988  MaxExitSpeed = 1; // to prevent divide by zero error
4989  }
4990  // below as was
4992  double DeltaExitTimeToMaxInSecs;
4993  double DistanceToMax;
4994  if(PowerAtRail > 1) // added at v2.4.0
4995  {
4996  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4997  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4998  (1.5 * AValue * AValue);
4999  }
5000  else
5001  {
5002  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
5003  // these not really accurate but will be good enough
5004  DistanceToMax = EntryHalfLength / 2;
5005  }
5006  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax; // remaining distance to half length
5007  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
5008  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
5010  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
5011  }
5012  }
5013  }
5014 
5015  else // SPADFlag set
5016  {
5018  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
5019  if(ExitSpeedHalfSquared < 10)
5020  {
5021  ExitSpeedHalf = 0;
5022  }
5023  else
5024  {
5025  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
5026  }
5027  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
5028  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
5029  if(ExitSpeedFullSquared < 10)
5030  {
5031  ExitSpeedFull = 0;
5032  }
5033  else
5034  {
5035  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
5036  }
5037  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
5038 
5039  // check if the exit speed is < 80% of the stopping speed for the next element, and if so stop at end of this element
5040  // this is because would stop short of end of next element (in reality the time to reach the end of the next element
5041  // would be too short (could be so short as to make the train jump) as time is calculated purely on speed & brake rate);
5042  // 80% is used as the brake rate might be set to come to a halt at the end of the next element in which case the speed
5043  // will be the stopping speed.
5044  if(ExitSpeedFull > 0)
5045  {
5046  if(Track->TrackElementAt(746, CurrentTrackVectorPosition).TrackType == Points)
5047  {
5048  if((EntryPos == 0) || (EntryPos == 2))
5049  {
5050  if(Track->TrackElementAt(747, CurrentTrackVectorPosition).Attribute == 0)
5051  {
5052  ExitPos = 1;
5053  }
5054  else
5055  {
5056  ExitPos = 3;
5057  }
5058  }
5059  else
5060  {
5061  ExitPos = 0;
5062  }
5063  }
5064  else
5065  {
5066  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5067  }
5068  NextTrackVectorPosition = Track->TrackElementAt(748, CurrentTrackVectorPosition).Conn[ExitPos];
5069  NextEntryPos = Track->TrackElementAt(749, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5070  if(NextTrackVectorPosition > -1) // not a continuation or buffer
5071  {
5072  int NextElementLength;
5073  if(NextEntryPos > 1)
5074  {
5075  NextElementLength = (Track->TrackElementAt(750, NextTrackVectorPosition).Length23);
5076  }
5077  else
5078  {
5079  NextElementLength = (Track->TrackElementAt(751, NextTrackVectorPosition).Length01);
5080  }
5081  double NextStoppingSpeed = sqrt(3.6 * 3.6 * 2 * BrakeRate * NextElementLength);
5082  if(ExitSpeedFull < (0.8 * NextStoppingSpeed))
5083  {
5084  ExitSpeedFull = 0;
5085  }
5086  }
5087  }
5088  }
5089  // allow all values to be set normally in case need to brake, then test for zero power & need to coast
5090  if(PowerAtRail < 1) // new at v2.4.0 note that km/h/3.6 = m/s
5091  {
5092  // bring to a stop in 20 elements at 100km/h & assume each 100m long for calculating exit times but if on a continuation maintain speed <--NO,
5093  //change to BrakeRate = CoastingBrakeRate = 0.03 and calc times etc as normal - because of Albie Vowles' error report of 231223 where noticed
5094  //that failed train treated track lengths of > 2km as 100m so very noticeable. Keep going for exiting at continuation.
5095 
5096  //Coasting deceleration rate from paper 'Real-time train motion parameter estimation using an Unscented Kalman Filter' at
5097  //'https://www.sciencedirect.com/science/article/pii/S0968090X22002212'. In particular Fig 6 in section 4.3 shows coasting from 400sec to
5098  //1000sec corresponds to speed drop from 140km/h to 80km/h, i.e. 60km/h in 600sec, equivalent to 0.02777m/s/s deceleration, so use 0.03m/s/s.
5099 
5100  if(LeadElement > -1)
5101  {
5103  // don't stop on a continuation either entering or leaving
5104  {
5107  MaxExitSpeed = LimitingSpeed;
5108  BrakeRate = 0;
5109  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5110  // assume length is 50m for ease of calc
5111  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5112  Utilities->CallLogPop(2126);
5113  return;
5114  }
5115  }
5116  else if(MidElement > -1)
5117  {
5119  {
5122  MaxExitSpeed = LimitingSpeed;
5123  BrakeRate = 0;
5124  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5125  // assume length is 50m for ease of calc
5126  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5127  Utilities->CallLogPop(2127);
5128  return;
5129  }
5130  }
5131  else if(LagElement > -1)
5132  {
5134  {
5137  MaxExitSpeed = LimitingSpeed;
5138  BrakeRate = 0;
5139  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5140  // assume length is 50m for ease of calc
5141  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5142  Utilities->CallLogPop(2128);
5143  return;
5144  }
5145  }
5146 /* dropped at v2.18.1 in favour of CoastingBrakeRate which = 0.03m/s/s = see above explanation
5147  if(EntrySpeed > 7.5) // keep going for at least another element
5148  {
5149  ExitSpeedHalf = EntrySpeed - 2.5;
5150  ExitSpeedFull = EntrySpeed - 5;
5151  MaxExitSpeed = LimitingSpeed;
5152  BrakeRate = 0;
5153  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed - 1.25) / 86400.0));
5154  // assume length is 50m for ease of calc
5155  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf - 1.25) / 86400.0));
5156  Utilities->CallLogPop(2129);
5157  return;
5158  }
5159  else // stop immediately, don't enter next element, floating label had displayed last exit speed full on 2nd half move so with zero when fully on element
5160  {
5161  // will appear to have slowed at steady rate
5162  ExitSpeedHalf = 0;
5163  ExitSpeedFull = 0;
5164  MaxExitSpeed = LimitingSpeed;
5165  BrakeRate = 0;
5166  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
5167  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
5168  StoppedWithoutPower = true;
5169  Utilities->CallLogPop(2130);
5170  return;
5171  }
5172 */
5173  }
5174  // TempBrakeRate=MinSingle; TempBrakeRate=MaxSingle; TempBrakeRate=MinDouble; TempBrakeRate=MaxDouble;//included to stop warnings from unused declarations in math.hpp
5175  // TempBrakeRate=MinExtended; TempBrakeRate=MaxExtended; TempBrakeRate=MinComp; TempBrakeRate=MaxComp;//included to stop warnings from unused declarations in math.hpp
5176  Utilities->CallLogPop(707);
5177 }
5178 // ---------------------------------------------------------------------------
5179 /*
5180  bool TTrain::IsTerminalStation(int TrackVectorPosition, int EntryPos)
5181  {
5182  int NextExitPos;
5183  TTrackElement NextElement = Track->TrackElementAt(379, TrackVectorPosition), TempElement;
5184  if(TimetableVector.empty()) return false;
5185  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5186  while((NextElement.ActiveTrackElementName == TimetableVector.begin()->LocationName) && (NextElement.TrackType != Buffers))
5187  {
5188  //check for points & follow attribute, but don't worry about a derail as that dealt with elsewhere
5189  if((NextElement.TrackType != Points) || ((EntryPos != 0) && (EntryPos != 2)))
5190  {
5191  NextExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5192  }
5193  else if((NextElement.TrackType == Points) && ((EntryPos == 0) || (EntryPos == 2)))
5194  {
5195  if(NextElement.Attribute == 0) NextExitPos = 1; else NextExitPos = 3;
5196  }
5197  TempElement = Track->TrackElementAt(380, NextElement.Conn[NextExitPos]);//need temp as NextElement used in next step
5198  NextElement = TempElement;
5199  }
5200  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5201  if(NextElement.TrackType == Buffers) return true;
5202  return false;//shouldn't reach here but include to prevent compiler return warning
5203  }
5204 */
5205 // ---------------------------------------------------------------------------
5206 
5207 int TTrain::NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
5208 /*
5209  returns the number by which the train ActionVectorEntryPtr needs
5210  to be incremented to point to the location arrival entry or passtime entry before a change of direction. Used to display missed
5211  actions when a stop or pass location has been reached before other timetabled events have been carried out. If can't find it, or Name
5212  is "", -1 is returned. A change of direction is the limit of the search because a train may not stop at a location on the way out
5213  but stop on way back, and in these circumstances no actions have been missed. Stop indicates whether the train will stop at (true)
5214  or pass (false) the location.
5215 */{
5216  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NameInTimetableBeforeCDT," + Name + "," + HeadCode);
5217  Stop = false;
5218  if(TimetableFinished || (Name == ""))
5219  {
5220  Utilities->CallLogPop(957);
5221  return(-1);
5222  }
5224 /*added the following check at v2.11.1 because of a fault found in Kevin Smith's railway (notified 02/01/22). CH01 started at
5225 Chester behind the stop position, but when it departed and this function was called it found Chester again with no cdt
5226 before it (it went round the Liverpool Loop), so it stopped again when it reached the stop position and reported all intermediate stops as having been
5227 missed. This check is for the train just having departed from the station in question, and if it has then any further stations
5228 with the same name are ignored - i.e. it stops a train from stopping at the same station twice in succession.
5229 */
5230  if(Ptr > &TrainDataEntryPtr->ActionVector.at(0))
5231  {
5232  Ptr--;
5233  if((Ptr->DepartureTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5234  {
5235  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5236  {
5237  Utilities->CallLogPop(2444);
5238  return(-1);
5239  }
5240  }
5241  }
5242  // start looking from current pointer position
5243  for(TActionVectorEntry *Ptr = ActionVectorEntryPtr; Ptr < &TrainDataEntryPtr->ActionVector.back(); Ptr++)
5244  {
5245  if((Ptr->Command == "cdt") || (Ptr->FormatType == Repeat))
5246  {
5247  break;
5248  }
5249  if((Ptr->ArrivalTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5250  {
5251  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5252  {
5253  Stop = true;
5254  Utilities->CallLogPop(960);
5255  return (Ptr - ActionVectorEntryPtr);
5256  }
5257  }
5258  if((Ptr->EventTime > TDateTime(-1)) && (Ptr->LocationName == Name) && (Ptr->Command == "pas"))
5259  {
5260  Utilities->CallLogPop(1517);
5261  return (Ptr - ActionVectorEntryPtr);
5262  }
5263  }
5264  Utilities->CallLogPop(959);
5265  return(-1); // not found a valid entry
5266 }
5267 
5268 // ---------------------------------------------------------------------------
5269 
5271 /* Checks forward from train LeadElement, following leading point attributes but ignoring trailing point attributes,
5272  until finds either a train or a signal/buffers/continuation/loop. If finds a train returns false, else returns true.
5273  Ignores the call-on signal.
5274 */{
5275  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ClearToNextSignal" + "," + HeadCode);
5276  int ReturnVal = 0;
5277  int ElementCount = 0;
5278 /* dropped at v2.12.0 as takes up a great deal of time unnecessarily - substitute 1000 elements instead and return true (very unlikely to need to search this far [10km at min length])
5279  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
5280  {
5281  Track->TrackElementAt(1031, x).TempTrackMarker01 = false;
5282  Track->TrackElementAt(1032, x).TempTrackMarker23 = false;
5283  }
5284 */
5285  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition;
5286  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos;
5287 
5288  while(true)
5289  {
5290  if((Track->TrackElementAt(382, CurrentTrackVectorPosition).TrainIDOnElement > -1) && (Track->TrackElementAt(383,
5291  CurrentTrackVectorPosition).TrainIDOnElement != TrainID))
5292  {
5293  ReturnVal = 1;
5294  break;
5295  }
5296  if(((Track->TrackElementAt(384, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(385,
5297  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
5298  {
5299  ReturnVal = 2;
5300  break;
5301  }
5302  if((EntryPos < 2) && (Track->TrackElementAt(386, CurrentTrackVectorPosition).Config[1 - EntryPos] == Signal) && !Track->TrackElementAt(529,
5303  CurrentTrackVectorPosition).CallingOnSet && (LeadElement != CurrentTrackVectorPosition)) // CallingOnSet true when position lights lit,
5304  {//added LeadElement condition at v2.18.0 as train may be on the callon signal after CallOnSet false & don't want to return true for that
5305  ReturnVal = 3;
5306  break;
5307  }
5308 /* not needed at and after v2.12.0, see above
5309  if((Track->TrackElementAt(387, CurrentTrackVectorPosition).TrackType == Bridge) || (Track->TrackElementAt(388, CurrentTrackVectorPosition).TrackType == Crossover))
5310  {
5311  if((EntryPos < 2) && (Track->TrackElementAt(523, CurrentTrackVectorPosition).TempTrackMarker01))
5312  // must be a loop - reached same point as examined earlier
5313  {
5314  ReturnVal = 4;
5315  break;
5316  }
5317  else if((EntryPos > 1) && (Track->TrackElementAt(524, CurrentTrackVectorPosition).TempTrackMarker23))
5318  {
5319  ReturnVal = 4;
5320  break;
5321  }
5322  }
5323  else
5324  {
5325  if((Track->TrackElementAt(525, CurrentTrackVectorPosition).TempTrackMarker01) || (Track->TrackElementAt(526, CurrentTrackVectorPosition).TempTrackMarker23))
5326  {
5327  ReturnVal = 4;
5328  break;
5329  }
5330  }
5331  if(EntryPos < 2)
5332  {
5333  Track->TrackElementAt(389, CurrentTrackVectorPosition).TempTrackMarker01 = true;
5334  }
5335  else
5336  {
5337  Track->TrackElementAt(527, CurrentTrackVectorPosition).TempTrackMarker23 = true;
5338  }
5339 */
5340 
5341  if(Track->TrackElementAt(390, CurrentTrackVectorPosition).TrackType == Points)
5342  {
5343  if((EntryPos == 0) || (EntryPos == 2))
5344  {
5345  if(Track->TrackElementAt(391, CurrentTrackVectorPosition).Attribute == 0)
5346  {
5347  ExitPos = 1;
5348  }
5349  else
5350  {
5351  ExitPos = 3;
5352  }
5353  }
5354  else
5355  {
5356  ExitPos = 0;
5357  }
5358  }
5359  else
5360  {
5361  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5362  }
5363  NextTrackVectorPosition = Track->TrackElementAt(392, CurrentTrackVectorPosition).Conn[ExitPos];
5364  NextEntryPos = Track->TrackElementAt(393, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5365  CurrentTrackVectorPosition = NextTrackVectorPosition;
5366  EntryPos = NextEntryPos;
5367  ElementCount++;
5368  if(ElementCount > 1000)
5369  {
5370  ReturnVal = 4;
5371  break;
5372  }
5373  }
5374  if(ReturnVal == 1)
5375  {
5376  Utilities->CallLogPop(708);
5377  return(false);
5378  }
5379  if(ReturnVal == 2)
5380  {
5381  Utilities->CallLogPop(709);
5382  return(true);
5383  }
5384  if(ReturnVal == 3)
5385  {
5386  Utilities->CallLogPop(946);
5387  return(true);
5388  }
5389  if(ReturnVal == 4)
5390  {
5391  Utilities->CallLogPop(947);
5392  return(true);
5393  }
5394  else
5395  {
5396  throw Exception("Error - failed to set ReturnVal in ClearToNextSignal()");
5397  }
5398 }
5399 
5400 // ---------------------------------------------------------------------------
5401 
5403 /*
5404  Check whether calling-on conditions met - a) approaching train has stopped at a signal but not at a location;
5405  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
5406  change of direction (cdt), remaining here (Frh), or under signaller control);
5407  c) at least 1 platform available for the approaching train; d) points (if any) set for direct route into platform;
5408  e) approaching train is to stop at station; f) no more facing signals between train and platform; g) [dropped g]
5409  h) train in front preventing route being set far enough to release stop signal; i) train in front not exiting at continuation; j) signal must be within 4km of
5410  the stop platform; k) [dropped (k), now can set a reoute or part route into platform so can set points more easily.] l) no existing route conflicts with the route into the platform,
5411  m) not failed or stopped without power
5412  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or other route conflicts - if a partial route set than can still
5413  change points outside the route or have a route conflict if another route is set.
5414 */{
5415  if(Track->RouteFlashFlag || TrainFailed || StoppedWithoutPower) //failed & no power conditions added at v2.10.0
5416  {
5417  return(false); // don't want to create a new route from the stop signal if one is already in construction & can't call on if failed or no power
5418  }
5419  // some of the callingon route elements may be involved
5420  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CallingOnAllowed" + "," + HeadCode);
5421  bool PlatformFoundFlag = false, StopRequired = false, SkipRouteCheck = false, RouteOrPartRouteSet = false; // last added at v1.2.0
5422  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition, ElementNumber = 0, Distance = 0;
5423  int RouteStartPosition;
5424  // this is the track vector position of the start element for the new unrestricted route - one past the stop signal
5425  int PlatformPosition;
5426  // the track vector position of the first stop platfrom
5427  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos, RouteID;
5428  // not used here
5429  AnsiString LeadStationName = Track->TrackElementAt(395, LeadElement).ActiveTrackElementName; // still OK even if ""
5430  int LeadElementDistance = Track->TrackElementAt(1017, LeadElement).Length01; //added after 2.7.0 as don't want to add this to overall distance since train has already covered this distance
5431  // use Length01, may be wrong for points/crossovers/bridges but unlikely to occur in practice
5432  // must be stopped at a signal but not at a location & still in timetable (a)
5434  // no need to check for SignallerStopped as this function only called in Timetable mode
5435  {
5436  Utilities->CallLogPop(711);
5437  return(false);
5438  }
5439  while(true)
5440  {
5441  TTrackElement &CurrentTrackElement = Track->TrackElementAt(396, CurrentTrackVectorPosition);
5442  // don't look further than 4km ahead (j)
5443  if(Distance > (4000 + LeadElementDistance))
5444  {
5445  Utilities->CallLogPop(967);
5446  return(false);
5447  }
5448  // if find another train on an element in front, before find a valid platform, return false (c)
5449  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && !PlatformFoundFlag)
5450  {
5451  Utilities->CallLogPop(713);
5452  return(false);
5453  }
5454  // if find another train in front when there is a valid platform (keep searching after find a platform as train may still
5455  // be facing later on)
5456  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && PlatformFoundFlag) //<-can return true in error if other train on bridge
5457  { //on other track, but leave in as shouldn't cause
5458  // get LeadElement, if -1 return (could be exiting at continuation) (i) //any problems - see if anyone reports it and if
5459  TTrain OtherTrain = TrainController->TrainVectorAtIdent(12, CurrentTrackElement.TrainIDOnElement); //so try to correct
5460  if(OtherTrain.LeadElement == -1)
5461  {
5462  Utilities->CallLogPop(714);
5463  return(false);
5464  }
5465  // if a facing train then make sure it is awaiting a join (Fjo or jbo) or a change of direction (cdt), or remaining here (Frh) (b)
5466  if(OtherTrain.LeadElement == CurrentTrackVectorPosition)
5467  {
5468  AnsiString OtherCommand = OtherTrain.ActionVectorEntryPtr->Command;
5469  if((OtherCommand == "Fjo") || (OtherCommand == "jbo") || (OtherCommand == "cdt") || (OtherCommand == "Frh") ||
5470  (OtherTrain.TrainMode == Signaller))
5471  {
5472  break;
5473  }
5474  else
5475  {
5476  Utilities->CallLogPop(955);
5477  return(false);
5478  }
5479  }
5480  else // (h)
5481  {
5482  break;
5483  }
5484  }
5485  // if reach buffers or exit continuation return false (can set route)
5486  if(((CurrentTrackElement.TrackType == Buffers) || (CurrentTrackElement.TrackType == Continuation)) && (EntryPos == 1))
5487  {
5488  Utilities->CallLogPop(716);
5489  return(false);
5490  }
5491  // if reach forward signal (other than the one the train is waiting at) return false (can set route) (f)
5492  if((EntryPos < 2) && (CurrentTrackElement.Config[1 - EntryPos] == Signal) && (CurrentTrackVectorPosition != Track->TrackElementAt(404,
5494  {
5495  Utilities->CallLogPop(717);
5496  return(false);
5497  }
5498  // if reach a location that isn't in timetable return false - drop this as still can't set a route
5499 /*
5500  if((Track->TrackElementAt(405, CurrentTrackVectorPosition).ActiveTrackElementName != "") && (Track->TrackElementAt(406, CurrentTrackVectorPosition).ActiveTrackElementName != LeadStationName) &&
5501  (NameInTimetableBeforeCDT(14, Track->TrackElementAt(407, CurrentTrackVectorPosition).ActiveTrackElementName) == -1))
5502  {
5503  Utilities->CallLogPop(718);
5504  return false;
5505  }
5506 */
5507  // if reach a location that is in timetable set PlatformFoundFlag (but not if position is points set to diverge) (e)
5508  if((CurrentTrackElement.ActiveTrackElementName != "") && (CurrentTrackElement.ActiveTrackElementName != LeadStationName) &&
5509  (NameInTimetableBeforeCDT(15, CurrentTrackElement.ActiveTrackElementName, StopRequired) > -1))
5510  {
5511  if(StopRequired)
5512  {
5513  if((CurrentTrackElement.TrackType != Points) || ((CurrentTrackElement.TrackType == Points) && (CurrentTrackElement.Attribute == 0)))
5514  {
5515  if(!PlatformFoundFlag)
5516  {
5517  PlatformPosition = CurrentTrackVectorPosition;
5518  }
5519  // ensure this only set once at first valid platform position - the unrestricted route will end here
5520  PlatformFoundFlag = true;
5521  }
5522  }
5523  }
5524  // Drop this below - was to prevent call-on if front train had left the station. Criterion now is not that front
5525  // train has to be at station but that has to be before the next forward signal
5526 /*
5527  if((Track->TrackElementAt(411, CurrentTrackVectorPosition).ActiveTrackElementName == "") && (PlatformFoundFlag))
5528  {
5529  Utilities->CallLogPop(719);
5530  return false;
5531  }
5532 */
5533  // make sure points are followed correctly (d) & set ExitPos
5534  if(CurrentTrackElement.TrackType == Points)
5535  {
5536  if((EntryPos == 0) || (EntryPos == 2))
5537  {
5538  if(CurrentTrackElement.Attribute == 0)
5539  {
5540  ExitPos = 1;
5541  }
5542  else
5543  {
5544  ExitPos = 3;
5545  }
5546  }
5547  if(EntryPos == 1)
5548  {
5549  if(CurrentTrackElement.Attribute == 0)
5550  {
5551  ExitPos = 0;
5552  }
5553  else
5554  {
5555  Utilities->CallLogPop(720);
5556  return(false);
5557  }
5558  }
5559  if(EntryPos == 3)
5560  {
5561  if(CurrentTrackElement.Attribute == 1)
5562  {
5563  ExitPos = 0;
5564  }
5565  else
5566  {
5567  Utilities->CallLogPop(721);
5568  return(false);
5569  }
5570  }
5571  }
5572  else
5573  {
5574  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5575  }
5576  // check existing routes - if element forward of the signal (ElementNumber == 2) is AutoSignals then OK without further checks as this route must extend to
5577  // the next signal so must at least reach the station, also if have another route set (must be unrestricted) from either the stop signal or the element after it
5578  // to or towards the platform (& all points set correctly) then OK, otherwise reject if (1) there are any route elements already set from element
5579  // forward of element after the signal to & including the first platform element (covers crossover with other route set) or (2) a fouled diagonal (k)
5580  if(ElementNumber < 2)
5581  {
5582  SkipRouteCheck = true;
5583  }
5584  else
5585  {
5586  SkipRouteCheck = false;
5587  }
5588  if(ElementNumber == 1) // the stop signal
5589  {
5590  RouteStartPosition = CurrentTrackVectorPosition;
5591  }
5592 /*
5593  if(ElementNumber == 2)
5594  {
5595  if(AllRoutes->GetRouteTypeAndNumber(18, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->AutoSigsRoute) AutoSigs = true;
5596  else AutoSigs = false;
5597  if(AllRoutes->GetRouteTypeAndNumber(25, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->NotAutoSigsRoute) OtherFullRouteSet = true;
5598  }
5599 */
5600  if(ElementNumber > 1)
5601  {
5602  if(AllRoutes->GetRouteTypeAndNumber(26, CurrentTrackVectorPosition, EntryPos, RouteID) != AllRoutes->NoRoute)
5603  {
5604  RouteOrPartRouteSet = true;
5605  }
5606  else
5607  {
5608  RouteOrPartRouteSet = false;
5609  }
5610  }
5611  if(!SkipRouteCheck && !RouteOrPartRouteSet)
5612  {
5613  if(AllRoutes->TrackIsInARoute(16, CurrentTrackVectorPosition, EntryPos)) // must be a conflicting route
5614  {
5615  Utilities->CallLogPop(1859);
5616  return(false);
5617  }
5618  int ExitLink = CurrentTrackElement.Link[ExitPos];
5619  if((ExitLink == 1) || (ExitLink == 3) || (ExitLink == 7) || (ExitLink == 9))
5620  {
5621  if(AllRoutes->DiagonalFouledByRouteOrTrain(6, CurrentTrackElement.HLoc, CurrentTrackElement.VLoc, ExitLink))
5622  {
5623  Utilities->CallLogPop(1850);
5624  return(false);
5625  }
5626  }
5627  }
5628  // finished all checks, now update CurrentTrackVectorPosition & EntryPos for the next iteration
5629  if(EntryPos < 2)
5630  {
5631  Distance += CurrentTrackElement.Length01;
5632  }
5633  else
5634  {
5635  Distance += CurrentTrackElement.Length23;
5636  }
5637  NextTrackVectorPosition = CurrentTrackElement.Conn[ExitPos];
5638  NextEntryPos = CurrentTrackElement.ConnLinkPos[ExitPos];
5639  CurrentTrackVectorPosition = NextTrackVectorPosition;
5640  EntryPos = NextEntryPos;
5641  ElementNumber++;
5642  } // while(true)
5643 
5644  // if all OK & autosigs route not already set then set an unrestricted route into the station (just to the first platform)
5645  // from the stop signal (note that it may be last in an autosigs route)
5646  // a single element route at the stop signal should have been removed prior to this function being called (that called before
5647  // this in ClockTimer2)
5648 
5649  // now add elements to the CallonVector
5650  TAllRoutes::TCallonEntry CallonEntry(RouteOrPartRouteSet, RouteStartPosition, PlatformPosition);
5651 
5652  AllRoutes->CallonVector.push_back(CallonEntry);
5653  Utilities->CallLogPop(1860);
5654  return(true); // return false if fail to set route for any reason
5655 }
5656 
5657 // ---------------------------------------------------------------------------
5658 /*
5659  bool TTrain::TimetableFinished()
5660  {
5661  if((ActionVectorEntryPtr == TrainDataEntryPtr->ActionVector.end()) || (ActionVectorEntryPtr->FormatType == Repeat))//past all actions
5662  {
5663  return true;
5664  }
5665  return false;
5666  }
5667 */
5668 // ---------------------------------------------------------------------------
5669 
5670 AnsiString TTrain::GetTrainHeadCode(int Caller)
5671 
5672 {
5673  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainHeadCode" + "," + HeadCode);
5674  AnsiString RepeatHeadCode = TrainController->GetRepeatHeadCode(0, HeadCode, RepeatNumber, IncrementalDigits);
5675 
5676  Utilities->CallLogPop(1452);
5677  return(RepeatHeadCode);
5678 }
5679 
5680 // ---------------------------------------------------------------------------
5681 
5682 TDateTime TTrain::GetTrainTime(int Caller, TDateTime Time)
5683 {
5684  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainTime," + Utilities->Format96HHMMSS(Time));
5685  TDateTime RepeatTime = TrainController->GetRepeatTime(1, Time, RepeatNumber, IncrementalMinutes);
5686 
5687  Utilities->CallLogPop(1453);
5688  return(RepeatTime);
5689 }
5690 
5691 // ---------------------------------------------------------------------------
5692 
5693 bool TTrain::IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
5694 {
5695  // Used to check for a stopped adjacent train for use in PopUp menu //new at v2.4.0
5696  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsThereAnAdjacentTrain" + "," + HeadCode);
5697  // check if there's a stopped adjacent train, if there is but not under sig control give a message in calling function
5698  // first check that train is fully on the railway
5699  bool FrontValid = false, RearValid = false;
5700  TTrackElement FrontAdjacentTrackElement, RearAdjacentTrackElement;
5701 
5702  if((LeadElement == -1) || (MidElement == -1))
5703  {
5704  TrainToBeJoinedBy = NULL;
5705  Utilities->CallLogPop(2131);
5706  return(false);
5707  }
5709  {
5710  FrontAdjacentTrackElement = Track->TrackElementAt(965, (Track->TrackElementAt(966, LeadElement).Conn[LeadExitPos]));
5711  FrontValid = true;
5712  }
5714  {
5715  RearAdjacentTrackElement = Track->TrackElementAt(968, (Track->TrackElementAt(969, MidElement).Conn[MidEntryPos]));
5716  RearValid = true;
5717  }
5718  int TrainToBeJoinedByID = -1;
5719 
5720  // first check if on a 2-track element & select correct ID number if so
5721  if(FrontValid)
5722  {
5723  if(FrontAdjacentTrackElement.TrackType == Bridge)
5724  {
5726  {
5727  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5728  }
5729  else
5730  {
5731  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5732  }
5733  }
5734  else
5735  {
5736  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnElement;
5737  }
5738  }
5739  if((TrainToBeJoinedByID < 0) && RearValid)
5740  {
5741  // first check if on a 2-track element & select correct ID number if so
5742  if(RearAdjacentTrackElement.TrackType == Bridge)
5743  {
5745  {
5746  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5747  }
5748  else
5749  {
5750  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5751  }
5752  }
5753  else
5754  {
5755  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnElement;
5756  }
5757  }
5758  if(TrainToBeJoinedByID < 0) // no adjacent train
5759  {
5760  TrainToBeJoinedBy = NULL;
5761  Utilities->CallLogPop(2132);
5762  return(false);
5763  }
5764  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(44, TrainToBeJoinedByID));
5765  if(!TrainToBeJoinedBy->Stopped())
5766  {
5767  TrainToBeJoinedBy = NULL;
5768  Utilities->CallLogPop(2133);
5769  return(false);
5770  }
5771  Utilities->CallLogPop(2134);
5772  return(true);
5773 }
5774 
5775 // ---------------------------------------------------------------------------
5776 
5777 void TTrain::LogAction(int Caller, AnsiString OwnHeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution,
5778  TDateTime TimetableNonRepeatTime, bool Warning)
5779 /*
5780  Time = timetable time, the time adjustments for repeat trains is carried out internally
5781  Not all messages need this, if not needed a dummy value is required but not used
5782 
5783  Arrive: 06:05:40: 2F46 arrived at Old Street 1 minute late (Min. Dwell Time xxx)
5784  Pass: 06:05:40: 2F46 passed Old Street 1 minute late
5785  Terminate: 06:05:40: 2F46 terminated at Old Street 1 minute late <-- sent from RemainHere as LogAction not called for terminate
5786  //NB for Frh just give terminated message but without event time - don't use this function
5787  Depart: 06:05:15: 3F43 departed from Essex Road 2 minutes late
5788  Create: 06:05:40: 2F46 created at Old Street 1 minute late
5789  Enter: 06:05:40: 2F46 entered railway at Old Street 1 minute late
5790  Leave: 06:05:40: 2F46 left railway at 57-N4 1 minute late
5791  FrontSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from front to 3D54 at Old Street 1 minute late
5792  RearSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from rear to 3D54 at Old Street 1 minute late
5793  JoinedByOther: 06:05:40: 2F46 joined by 3D54 at Old Street 1 minute late
5794  ChangeDirection: 06:05:40: 2F46 changed direction at Old Street 1 minute late
5795  ChangeDescription: 06:05:40: 2F46 changed its description to 'NewDescription' at Old Street 1 minute late
5796  ChangeMaxSpeed: 06:05:40: 2F46 changed its maximum speed to 'NewMaxSpeed' at Old Street 1 minute late
5797  NewService: 06:05:40: 2F46 became new service 3D54 at Old Street 1 minute late
5798  TakeManualControl: 06:05:40: 2F46 taken under signaller control at Old Street
5799  RestoreTimetableControl: 06:05:40: 2F46 restored to timetable control at Old Street
5800  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO CRASH at Old Street
5801  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO DERAILMENT at Old Street
5802  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY at Old Street
5803  SignallerMoveForwards 06:05:40: 2F46 received signaller authority to proceed
5804  SignallerChangeDirection 06:05:40: 2F46 changed direction under signaller control at Old Street
5805  SignallerPassRedSignal 06:05:40: 2F46 received signaller authority to pass stop signal
5806  SignallerJoin 06:05:40: 2F46 joined under signaller control by 3D54 at Old Street //new at v2.4.0
5807  TrainFailure 06:05:40: 2F46 suffered an onboard power failure at Old Street //new at v2.4.0
5808  RepairFailedTrain 06:05:40: 2F46 failure repaired at Old Street //new at v2.4.0
5809  SignallerControlStop 06:05:40: 2F46 received signaller instruction to stop
5810  SignallerStop 06:05:40: 2F46 stopped on signaller command
5811  SignallerLeave: 06:05:40: 2F46 left railway under signaller control at 57-N4
5812  SignallerStepForward: 06:05:40: 2F46 received signaller authority to step forward
5813 */{
5814  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogAction," + OwnHeadCode + "," + OtherHeadCode + "," +
5815  AnsiString(ActionType) + "," + LocationName + "," + HeadCode);
5816  AnsiString BaseLog = "", WarningBaseLog = "", ReminderBaseLog = "", PerfLog = "", ActionLog = "";
5817  int IntMinsLate = 0;
5818  bool TTEvent = false; //indicates a timetabled event, prevents reminders for non-tt events where these set for next tt event
5819  //don't need it for warnings as these passed in from appropriate calling functions
5820  // need to set it in case MinsLate == 0, since it isn't tested for that
5821  if(ActionType == Arrive)
5822  {
5823  ActionLog = " arrived at ";
5824  TTEvent = true;
5825  }
5826  if(ActionType == Terminate) //redundant as Logaction not called for terminate - RemainHere deals with logging for terminate
5827  {
5828  if(TerminatedMessageSent) // to avoid it being sent twice
5829  {
5830  Utilities->CallLogPop(1104);
5831  return;
5832  }
5833  ActionLog = " terminated at ";
5834  TTEvent = true;
5835  TerminatedMessageSent = true;
5836  }
5837  if(ActionType == Depart)
5838  {
5839  ActionLog = " departed from ";
5840  TTEvent = true;
5841  }
5842  if(ActionType == Pass)
5843  {
5844  ActionLog = " passed ";
5845  TTEvent = true;
5846  }
5847  if(ActionType == Create)
5848  {
5849  ActionLog = " created at ";
5850  }
5851  if(ActionType == Enter)
5852  {
5853  ActionLog = " entered railway at ";
5854  }
5855  if(ActionType == ChangeDescription)
5856  {
5857  ActionLog = " changed its description to '" + Description + "' at "; //change to train description at v2.16.1
5858  TTEvent = true;
5859  }
5860  if(ActionType == ChangeMaxSpeed)
5861  {
5862  ActionLog = " changed its maximum speed to " + ActionVectorEntryPtr->NewMaxSpeed + " at "; //change to train max speed at v2.21.0
5863  TTEvent = true;
5864  }
5865  if(ActionType == Leave)
5866  {
5867  ActionLog = " left railway at ";
5868  TTEvent = true;
5869  }
5870  if(ActionType == FrontSplit)
5871  {
5872  ActionLog = " split mass%-Power% = " + SplitDistribution + " from front to ";
5873  TTEvent = true;
5874  }
5875  if(ActionType == RearSplit)
5876  {
5877  ActionLog = " split mass%-Power% = " + SplitDistribution + " from rear to ";
5878  TTEvent = true;
5879  }
5880  if(ActionType == JoinedByOther)
5881  {
5882  ActionLog = " joined by ";
5883  TTEvent = true;
5884  }
5885  if(ActionType == ChangeDirection)
5886  {
5887  ActionLog = " changed direction at ";
5888  TTEvent = true;
5889  }
5890  if(ActionType == NewService)
5891  {
5892  ActionLog = " became new service ";
5893  TTEvent = true;
5894  }
5895  if(ActionType == TakeSignallerControl)
5896  {
5897  ActionLog = " taken under signaller control at ";
5898  }
5899  if(ActionType == RestoreTimetableControl)
5900  {
5901  ActionLog = " restored to timetable control at ";
5902  }
5903  if(ActionType == RemoveTrain)
5904  {
5905  if(Crashed)
5906  {
5907  ActionLog = " REMOVED FROM RAILWAY DUE TO CRASH at ";
5908  }
5909  else if(Derailed)
5910  {
5911  ActionLog = " REMOVED FROM RAILWAY DUE TO DERAILMENT at ";
5912  }
5913  else
5914  {
5915  ActionLog = " REMOVED FROM RAILWAY at ";
5916  }
5917  }
5918  if(ActionType == SignallerMoveForwards)
5919  {
5920  ActionLog = " received signaller authority to proceed";
5921  }
5922  if(ActionType == SignallerStepForward)
5923  {
5924  ActionLog = " received signaller authority to step forward";
5925  }
5926  if(ActionType == SignallerChangeDirection)
5927  {
5928  ActionLog = " changed direction under signaller control at ";
5929  }
5930  if(ActionType == SignallerPassRedSignal)
5931  {
5932  ActionLog = " received signaller authority to pass stop signal";
5933  }
5934  if(ActionType == SignallerControlStop)
5935  {
5936  ActionLog = " received signaller instruction to stop";
5937  }
5938  if(ActionType == SignallerStop)
5939  {
5940  ActionLog = " stopped on signaller instruction ";
5941  }
5942  if(ActionType == SignallerJoin)
5943  {
5944  ActionLog = " joined under signaller control by ";
5945  }
5946  if(ActionType == TrainFailure)
5947  {
5948  ActionLog = " suffered an onboard power failure at ";
5949  }
5950  if(ActionType == RepairFailedTrain)
5951  {
5952  ActionLog = " failure repaired at ";
5953  }
5954  if(ActionType == SignallerLeave)
5955  {
5956  ActionLog = " left railway under signaller control at ";
5957  }
5958  if(OtherHeadCode != "")
5959  {
5960  OtherHeadCode += " at ";
5961  }
5962  TDateTime ActualTime = TrainController->TTClockTime;
5963 
5964  if(Warning)
5965  {
5966  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5967  WarningBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5968  }
5969  else //added at v2.19.0
5970  {
5971  if(TTEvent && ((ActionVectorEntryPtr->Reminder == 1) || (ActionVectorEntryPtr->Reminder == 4)))
5972  {
5973  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5974  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5975  }
5976  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 2) && (ActionLog == " departed from ")) //depart only
5977  {
5978  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5979  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5980  }
5981  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 3) && (ActionLog == " arrived at ")) //arrive only
5982  {
5983  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5984  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5985  }
5986  else
5987  {
5988  BaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5989  }
5990  }
5991 
5992  bool TimePerformance = true;
5993  AnsiString MinMinsString = "";
5994  if((ActionType == TakeSignallerControl) || (ActionType == RestoreTimetableControl) || (ActionType == RemoveTrain) || (ActionType == SignallerMoveForwards)
5995  || (ActionType == SignallerChangeDirection) || (ActionType == SignallerPassRedSignal) || (ActionType == SignallerControlStop) ||
5996  (ActionType == SignallerStop) || (ActionType == SignallerLeave) || (ActionType == SignallerStepForward) || (ActionType == SignallerJoin) ||
5997  (ActionType == TrainFailure) || (ActionType == RepairFailedTrain))
5998  // SignallerJoin & RepairFailedTrain new at v2.4.0
5999  {
6000  TimePerformance = false;
6001  }
6002  if(TimePerformance)
6003  {
6004  double MinsLate = ((double)(ActualTime - GetTrainTime(1, TimetableNonRepeatTime))) * 1440;
6005  MinsDelayed = float(MinsLate);
6006  if(ActionType == Pass) //added at v2.9.2 to prevent time to act increasing suddenly for early pass times then becoming 'NOW' when stops at signal
6007  {
6008  MinsDelayed = 0;
6009  }
6010  // new v2.2.0 for OpActionPanel, can be positive or negative
6011  if(ActionType == Arrive)
6012  {
6014  }
6015  // since train has just arrived this value is the
6016  // recoverable time available at this stop, so reduce MinsDelayed by this amount to prevent it being
6017  // subtracted from later stop recoverable times.
6018  if(MinsLate < 0)
6019  {
6020  IntMinsLate = int(ceil(MinsLate));
6021  }
6022  if(MinsLate > 0)
6023  {
6024  IntMinsLate = int(floor(MinsLate));
6025  }
6026  if(IntMinsLate == 0)
6027  {
6028  PerfLog = " on time";
6029  }
6030  else if(IntMinsLate == 1)
6031  {
6032  PerfLog = " 1 minute late";
6033  }
6034  else if(IntMinsLate == -1)
6035  {
6036  PerfLog = " 1 minute early";
6037  }
6038  else if(IntMinsLate > 1)
6039  {
6040  PerfLog = " " + AnsiString(IntMinsLate) + " minutes late";
6041  }
6042  else if(IntMinsLate < -1)
6043  {
6044  int PosIntMinsLate = -IntMinsLate;
6045  PerfLog = " " + AnsiString(PosIntMinsLate) + " minutes early";
6046  }
6047  if(LocationName.Pos('-') > 0)
6048  {
6049  PerfLog = "," + PerfLog;
6050  // if a position add a comma to separate vertical position number from number of minutes (better appearance)
6051  }
6052  if((ActionType == Arrive) && (ArrivalMinDwellTime > 30.1)) //add 0.1 to avoid rounding errors, can't use ArrivalMinDwellTime as not set until UpdateTrain
6053  {
6054  double MDTdouble = ArrivalMinDwellTime / 60;
6055  double MDT = int(MDTdouble * 10);
6056  MDT = MDT / 10;
6057  MinMinsString = "mins";
6058  if((MDT < 1.1) && (MDT > 0.9))
6059  {
6060  MinMinsString = "min";
6061  }
6062  PerfLogForm->PerformanceLog(0, BaseLog + PerfLog + " (Min. Dwell Time " + MDT + MinMinsString + ')');
6063  }
6064  else
6065  {
6066  PerfLogForm->PerformanceLog(68, BaseLog + PerfLog);
6067  }
6068  }
6069  else
6070  {
6071  PerfLogForm->PerformanceLog(1, BaseLog);
6072  }
6073  if(Warning)
6074  {
6075  Display->WarningLog(0, WarningBaseLog);
6076  }
6077  if(ReminderBaseLog != "") //added at v2.19.0
6078  {
6079  Display->WarningLog(24, ReminderBaseLog);
6080  ReminderBaseLog = ""; //reset to null
6081  ActionVectorEntryPtr->Reminder = 0; //to prevent reminders for repeats
6082  }
6083  // update statistics
6084  if((ActionType == Arrive) && (IntMinsLate == 0))
6085  {
6087  }
6088  else if((ActionType == Arrive) && (IntMinsLate > 0))
6089  {
6091  TrainController->TotLateArrMins += IntMinsLate;
6092  }
6093  else if((ActionType == Arrive) && (IntMinsLate < 0))
6094  {
6096  TrainController->TotEarlyArrMins += abs(IntMinsLate);
6097  }
6098 
6099  else if((ActionType == Pass) && (IntMinsLate == 0))
6100  {
6102  }
6103  else if((ActionType == Pass) && (IntMinsLate > 0))
6104  {
6106  TrainController->TotLatePassMins += IntMinsLate;
6107  }
6108  else if((ActionType == Pass) && (IntMinsLate < 0))
6109  {
6111  TrainController->TotEarlyPassMins += abs(IntMinsLate);
6112  }
6113 
6114  else if((ActionType == Leave) && (IntMinsLate == 0)) //new at v2.9.1 as had been omitted in error earlier
6115  {
6117  }
6118  else if((ActionType == Leave) && (IntMinsLate > 0))
6119  {
6121  TrainController->TotLateExitMins += IntMinsLate;
6122  }
6123  else if((ActionType == Leave) && (IntMinsLate < 0))
6124  {
6126  TrainController->TotEarlyExitMins += abs(IntMinsLate);
6127  }
6128 
6129  else if((ActionType == Depart) && (IntMinsLate == 0)) //can't depart early
6130  {
6132  }
6133  else if((ActionType == Depart) && (IntMinsLate > 0))
6134  {
6136  TrainController->TotLateDepMins += IntMinsLate;
6137  }
6138  Utilities->CallLogPop(968);
6139 }
6140 
6141 // ---------------------------------------------------------------------------
6142 
6143 void TTrain::TrainHasFailed(int Caller)
6144 {
6145  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainHasFailed," + HeadCode);
6146  if(Crashed || Derailed || DerailPending)
6147  {
6148  TrainFailurePending = false;
6149  Utilities->CallLogPop(2135);
6150  return;
6151  }
6152  AnsiString LocName = "";
6153 
6154  if(LeadElement > -1)
6155  {
6157  }
6158  if((LocName == "") && (MidElement > -1))
6159  {
6161  }
6162  if((LocName == "") && LeadElement > -1)
6163  {
6164  LocName = Track->TrackElementAt(974, LeadElement).ElementID;
6165  }
6166  if((LocName == "") && (MidElement > -1))
6167  {
6168  LocName = Track->TrackElementAt(975, MidElement).ElementID;
6169  }
6170  TrainController->StopTTClockMessage(81, HeadCode + " has suffered an onboard power failure at " + LocName);
6171  TrainFailed = true;
6172  TrainFailurePending = false;
6173  CallingOnFlag = false; //added at v2.10.0
6175  PowerAtRail = 0.08;
6176  AValue = sqrt(2 * PowerAtRail / Mass);
6178  // TrainFailed only called when PlotElements properly set to Lead, Mid & Lag elements
6179  if(Stopped())
6180  {
6181  EntrySpeed = 0;
6182  ExitSpeedHalf = 0;
6183  ExitSpeedFull = 0;
6184  MaxExitSpeed = 0;
6185  BrakeRate = 0;
6186  StoppedWithoutPower = true;
6187  }
6189  LogAction(33, HeadCode, "", TrainFailure, LocName, "", TDateTime(0), true);
6190  // true for warning, TDateTime not used
6191  Utilities->CallLogPop(2136);
6192 }
6193 
6194 // ---------------------------------------------------------------------------
6195 
6196 void TTrain::FrontTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6197 {
6198 /*
6199  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6200  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6201  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6202  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6203 
6204  Note that for front split only the continuing service has the min dwell time as the front needs to go first & keeps to timetable is possible
6205 */
6206  TrainController->LogEvent("" + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6207  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6208 
6209 /* restriction removed at v2.19.0
6210  if(PowerAtRail < 1)
6211  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6212  {
6213  if(!ZeroPowerNoFrontSplitMessage)
6214  {
6215  TrainController->StopTTClockMessage(82, HeadCode + ": A train without power can't split");
6216  }
6217  ZeroPowerNoFrontSplitMessage = true;
6218  Utilities->CallLogPop(2137);
6219  return;
6220  }
6221 */
6222 
6223  AnsiString LocationName = Track->TrackElementAt(555, LeadElement).ActiveTrackElementName;
6224 
6225  if(LocationName == "")
6226  {
6227  LocationName = Track->TrackElementAt(837, MidElement).ActiveTrackElementName;
6228  }
6229  int RearTrainRearPos, RearTrainFrontPos, RearTrainExitPos;
6230  int FrontTrainRearPos, FrontTrainFrontPos;
6232 
6233  if(LocationName == "")
6234  {
6235  throw Exception("Error - LocationName not set in FrontTrainSplit");
6236  }
6237  // if message given call at ~5 sec intervals in case train repositioned
6238 
6239  bool TemporaryDelay = false;
6241  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6242  {
6244  {
6246  {
6247  TrainController->StopTTClockMessage(151, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6248  TrainController->LogActionError(6, HeadCode, "", FailLocTooShort, LocationName);
6250  }
6251  }
6252  Utilities->CallLogPop(1009); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6253  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6254  } //found & not reported. Same for RearTrainSplit
6255 
6256  if(TemporaryDelay)
6257  {
6259  Utilities->CallLogPop(2683);
6260  return;
6261  }
6262 
6263 //it is long enough for split
6264  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6265  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6266  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6268 
6269  UnplotTrain(0);
6270  StartSpeed = 0;
6271  RearStartElement = RearTrainRearPos; //this is for the current train, not the new train which will attach to the front of this train
6272  for(int x = 0; x < 4; x++)
6273  {
6274  if(Track->TrackElementAt(1664, RearStartElement).Conn[x] == RearTrainFrontPos)
6275  {
6276  RearStartExitPos = x;
6277  }
6278  }
6279  StoppedAtLocation = true;
6280  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6281  {
6282  StoppedWithoutPower = true;
6283  }
6284  PlotStartPosition(3);
6287 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6289 
6290  //new at v2.15.0 for unequal split in mass & power
6291  int NewTrainMass;
6292  double NewTrainPowerAtRail;
6294  {
6295  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6296  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6297  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6298  NewTrainMass = Mass * double(MassPercent)/100.0;
6299  Mass = Mass - NewTrainMass;
6300  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6301  if(NewTrainPowerAtRail == 0)
6302  {
6303  NewTrainPowerAtRail = 0.08; //min value represents 0
6304  }
6305  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6306  AValue = sqrt(2 * PowerAtRail / Mass);
6307  }
6308  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6309  {
6310  Mass = Mass / 2;
6311  NewTrainMass = Mass;
6312  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6313  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6314  // and when needed it's calculated from rate & mass - changed at v2.15.0
6315  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6316  PowerAtRail = PowerAtRail / 2;
6317  NewTrainPowerAtRail = PowerAtRail;
6318  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6319  AValue = sqrt(2 * PowerAtRail / Mass);
6320  // shouldn't change but include in case not set earlier
6321  }
6322 
6323  TActionEventType EventType = NoEvent;
6324  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6325  //may be repositioned so all references to this train may be invalid
6326 
6327  if(!TrainController->AddTrain(0, FrontTrainRearPos, FrontTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6328  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6329  // false for SignallerControl
6330  {
6331  Utilities->CallLogPop(1721); // EventType not used here
6332  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6333  // another train, in which case a message will have been sent to the perf log, also might well clear later
6334  // when other train moves away
6335  return;
6336  }
6337 
6338  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6339  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6340  {
6341 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6342  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6343  }
6344  TrainController->TrainVector.back().ActualArrivalTime = ActualArrivalTime; //added at v2.23.0, both trains take same arrival time
6345  TrainController->TrainVector.back().ArrivalMinDwellTime = ArrivalMinDwellTime; //added at v2.23.0, split train uses original MDT
6346  ArrivalMinDwellTime = ArrivalMinDwellTime + 30.0; //added at v2.23.0, original train adds 30 secs so split train departs first
6347  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6348  // see mods in UpdateTrain for v1.3.2
6349  TrainController->TrainAdded = true;
6350 
6351  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6352 
6353  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6354  TTOD.RunningEntry = Running;
6355  Utilities->CallLogPop(998);
6356 }
6357 
6358 // ---------------------------------------------------------------------------
6359 
6360 void TTrain::RearTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6361 {
6362 /*
6363  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6364  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6365  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6366  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6367 */
6368  TrainController->LogEvent("" + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6369  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6370 
6371 /* restriction removed at v2.19.0
6372  if(PowerAtRail < 1)
6373  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6374  {
6375  if(!ZeroPowerNoRearSplitMessage)
6376  {
6377  TrainController->StopTTClockMessage(176, HeadCode + ": A train without power can't split");
6378  }
6379  ZeroPowerNoRearSplitMessage = true;
6380  Utilities->CallLogPop(2685);
6381  return;
6382  }
6383 */
6384  AnsiString LocationName = Track->TrackElementAt(1676, LeadElement).ActiveTrackElementName;
6385 
6386  if(LocationName == "")
6387  {
6388  LocationName = Track->TrackElementAt(1677, MidElement).ActiveTrackElementName;
6389  }
6390  int RearTrainRearPos, RearTrainFrontPos;
6391  int FrontTrainRearPos, FrontTrainFrontPos, FrontTrainExitPos;
6393 
6394  if(LocationName == "")
6395  {
6396  throw Exception("Error - LocationName not set in RearTrainSplit");
6397  }
6398  // if message given call at ~5 sec intervals in case train repositioned
6399 
6400  bool TemporaryDelay = false;
6402  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6403  {
6405  {
6407  {
6408  TrainController->StopTTClockMessage(177, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6409  TrainController->LogActionError(66, HeadCode, "", FailLocTooShort, LocationName);
6411  }
6412  }
6413  Utilities->CallLogPop(2686); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6414  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6415  } //found & not reported. Same for FrontTrainSplit
6416 
6417  if(TemporaryDelay)
6418  {
6420  Utilities->CallLogPop(2684);
6421  return;
6422  }
6423 
6424 //it is long enough for split
6425  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6426  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6427  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6429 
6430  UnplotTrain(11);
6431  StartSpeed = 0;
6432  RearStartElement = FrontTrainRearPos; //this is for the current train, not the new train which will attach to the rear of this train
6433  for(int x = 0; x < 4; x++)
6434  {
6435  if(Track->TrackElementAt(1665, RearStartElement).Conn[x] == FrontTrainFrontPos)
6436  {
6437  RearStartExitPos = x;
6438  }
6439  }
6440  StoppedAtLocation = true;
6441  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6442  {
6443  StoppedWithoutPower = true;
6444  }
6445  PlotStartPosition(12);
6448 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6450 
6451  //new at v2.15.0 for unequal split in mass & power
6452  int NewTrainMass;
6453  double NewTrainPowerAtRail;
6455  {
6456  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6457  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6458  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6459  NewTrainMass = Mass * double(MassPercent)/100.0;
6460  Mass = Mass - NewTrainMass;
6461  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6462  if(NewTrainPowerAtRail == 0)
6463  {
6464  NewTrainPowerAtRail = 0.08; //min value represents 0
6465  }
6466  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6467  AValue = sqrt(2 * PowerAtRail / Mass);
6468  }
6469  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6470  {
6471  Mass = Mass / 2;
6472  NewTrainMass = Mass;
6473  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6474  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6475  // and when needed it's calculated from rate & mass - changed at v2.15.0
6476  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6477  PowerAtRail = PowerAtRail / 2;
6478  NewTrainPowerAtRail = PowerAtRail;
6479  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6480  AValue = sqrt(2 * PowerAtRail / Mass);
6481  // shouldn't change but include in case not set earlier
6482  }
6483 
6484  TActionEventType EventType = NoEvent;
6485  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6486  //may be repositioned so all references to this train may be invalid
6487 
6488  if(!TrainController->AddTrain(4, RearTrainRearPos, RearTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6489  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6490  // false for SignallerControl
6491  {
6492  Utilities->CallLogPop(2687); // EventType not used here
6493  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6494  // another train, in which case a message will have been sent to the perf log, also might well clear later
6495  // when other train moves away
6496  return;
6497  }
6498 
6499  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6500  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6501  {
6502 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6503  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6504  }
6505  TrainController->TrainVector.back().ActualArrivalTime = ActualArrivalTime; //added at v2.23.0, both trains take same arrival time
6506  TrainController->TrainVector.back().ArrivalMinDwellTime = ArrivalMinDwellTime + 30.0; //added at v2.23.0, split train uses MDT + 30 so original train departs first
6507 
6508  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6509  // see mods in UpdateTrain for v1.3.2
6510  TrainController->TrainAdded = true;
6511 
6512  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6513 
6514  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6515  TTOD.RunningEntry = Running;
6516  Utilities->CallLogPop(2688);
6517 }
6518 
6519 // ---------------------------------------------------------------------------
6520 
6521 void TTrain::FinishJoin(int Caller) //this just sends appropriate messages, the main work including deleting this train is done by JoinedBy - see below
6522 {
6523  if(FinishJoinLogSent == false)
6524  {
6525  TrainController->LogEvent("" + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6526  FinishJoinLogSent = true; // so don't keep logging this event
6527  // don't need to reset it to false after the event as the train is deleted
6528  }
6529  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6530  if(TrainFailed)
6531  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when repaired) Can FinishJoin if zero power & not failed, as for empty stock
6532  {
6534  {
6535  TrainController->StopTTClockMessage(84, HeadCode + ": A failed train can't join another under timetable control");
6536  }
6538  Utilities->CallLogPop(2139);
6539  return;
6540  }
6541  if(TrainGone)
6542  // this means that the train has already joined the other & is awaiting deletion by TrainController
6543  // without this the 'waiting' message can be given since the other train's ActionVectorEntryPtr has moved
6544  // on from jbo & TrainToJoinIsAdjacent returns false
6545  {
6546  Utilities->CallLogPop(1035);
6547  return;
6548  }
6549  TTrain *TrainToJoin;
6551 
6552  if(!TrainToJoinIsAdjacent(0, TrainToJoin))
6553  {
6555  {
6556  // PerfLogForm->PerformanceLog(2, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to join " + JBOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6559  }
6560  Utilities->CallLogPop(1030);
6561  return; // keep this here in case need to add code before final return
6562  }
6563  // no need to clear error report flag here, cleared in jbo function
6564  // No need to set TimetableFinished, done in jbo function, and this train deleted in jbo function
6565  // here when other train is adjacent
6566  Utilities->CallLogPop(1031);
6567 }
6568 
6569 // ---------------------------------------------------------------------------
6570 
6571 void TTrain::JoinedBy(int Caller)
6572 {
6573  if(TrainController->OpTimeToActUpdateCounter == 0) //added at v2.13.2. Use OpTimeToActUpdateCounter for convenience so only issue the event log
6574  //once every second rather than many times. Can't use an event logged flag because there may
6575  //be several trains that are to be joined by others
6576  {
6577  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Waiting to be joined");
6578  }
6579  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",JoinedBy" + "," + HeadCode);
6580 
6581 /* restriction removed at v2.19.0
6582  if(PowerAtRail < 1)
6583  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when power restored)
6584  {
6585  if(!ZeroPowerNoJoinedByMessage)
6586  {
6587  TrainController->StopTTClockMessage(85, HeadCode + ": A train without power can't be joined by another under timetable control");
6588  }
6589  ZeroPowerNoJoinedByMessage = true;
6590  Utilities->CallLogPop(2140);
6591  return;
6592  }
6593 */
6594  TTrain *TrainToBeJoinedBy;
6596 
6597  if(!TrainToBeJoinedByIsAdjacent(0, TrainToBeJoinedBy))
6598  {
6600  {
6601  // PerfLogForm->PerformanceLog(3, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to be joined by " + FJOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6604  }
6605  LastActionDelayFlag = true; //not used at v2.23.0 but keep as included in session file
6606  // need to update LastActionTime if this train first to arrive as need 30s after
6607  // both adjacent before the join
6608  Utilities->CallLogPop(1032);
6609  return;
6610  }
6611  // here when other train is adjacent
6612 // if(LastActionDelayFlag) //drop this as if Fjo train arrives first it won't be set
6613  {
6614  if(TrainToBeJoinedBy->LastActionTime > LastActionTime) //LastActionTime for this train already set when arrived
6615  {
6616  LastActionTime = TrainToBeJoinedBy->LastActionTime;
6617  }
6618  if((double(TrainToBeJoinedBy->ActualArrivalTime) + (TrainToBeJoinedBy->ArrivalMinDwellTime / 86400)) > (double(ActualArrivalTime) + (ArrivalMinDwellTime / 86400)))
6619  {
6620  ActualArrivalTime = TrainToBeJoinedBy->ActualArrivalTime;
6621  ArrivalMinDwellTime = TrainToBeJoinedBy->ArrivalMinDwellTime; //these set so that both trains get at least their required min dwell times
6622  }
6623  // need to update this as need 30s after both adjacent before the join
6624  if(TrainController->TTClockTime < (LastActionTime + TDateTime(30.0 / 86400))) //don't move on until 30s after last train to arrive + 30 secs
6625  {
6626  LastActionDelayFlag = false;
6627  Utilities->CallLogPop(1033);
6628  return;
6629  }
6630  }
6631  // here when other train is adjacent & 30 secs elapsed since both adjacent
6632 
6633  // set new values for mass etc
6634  if(MaxRunningSpeed > TrainToBeJoinedBy->MaxRunningSpeed)
6635  {
6636  MaxRunningSpeed = TrainToBeJoinedBy->MaxRunningSpeed;
6637  }
6638  double OtherBrakeForce = TrainToBeJoinedBy->MaxBrakeRate * TrainToBeJoinedBy->Mass;
6639  double OwnBrakeForce = MaxBrakeRate * Mass;
6640  double CombinedBrakeRate = (OtherBrakeForce + OwnBrakeForce) / (TrainToBeJoinedBy->Mass + Mass);
6641 
6642  Mass += TrainToBeJoinedBy->Mass;
6643  MaxBrakeRate = CombinedBrakeRate;
6644  PowerAtRail += TrainToBeJoinedBy->PowerAtRail;
6645  if(PowerAtRail > 1)
6646  {
6647  StoppedWithoutPower = false; //added at v2.23.2 when discovered in Micke's tram depot railway (train remained stopped after joined by powered train)
6648  }
6649  AValue = sqrt(2 * PowerAtRail / Mass);
6650 
6652  TrainToBeJoinedBy->TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
6653  TrainToBeJoinedBy->TimetableFinished = true;
6654  TrainToBeJoinedBy->TrainGone = true;
6655  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Joined By," + FJOHeadCode); //added at v2.13.2 to provide more information
6656  // this will cause other train to be deleted
6657  TrainToBeJoinedBy->JoinedOtherTrainFlag = true;
6661  Utilities->CallLogPop(1034);
6662 }
6663 
6664 // ---------------------------------------------------------------------------
6665 
6666 void TTrain::ChangeTrainDirection(int Caller, bool NoLogFlag)
6667 {
6668  TrainController->LogEvent("" + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6669  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6670 
6671  /* restriction removed at v2.19.0
6672  if(PowerAtRail < 1)
6673  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can change direction when power restored)
6674  {
6675  if(!ZeroPowerNoCDTMessage)
6676  {
6677  TrainController->StopTTClockMessage(86, HeadCode + ": A train without power can't change direction under timetable control");
6678  }
6679  ZeroPowerNoCDTMessage = true;
6680  Utilities->CallLogPop(2141);
6681  return;
6682  }
6683 */
6684  TColor TempColour = BackgroundColour;
6685 
6686  UnplotTrain(2);
6689  StartSpeed = 0;
6690  StoppedAtLocation = true;
6691  PlotStartPosition(1);
6692  PlotTrainWithNewBackgroundColour(27, TempColour, Display);
6693  // plot same as was - should always be pale green
6694  if(!NoLogFlag)
6695  {
6698  }
6700 
6701  //now erase a stub route if there is one, added at v2.5.1
6702  //first element of route is now immediately behind the train (i.e. next to MidElement)
6703  if(MidEntryPos >= 0)
6704  {
6705  TTrackElement MidTrackElement = Track->TrackElementAt(996, MidElement);
6706  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
6707  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
6708  int RouteNumber = -1;
6709  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(34, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
6710  if(RouteType == TAllRoutes::NotAutoSigsRoute)
6711  {
6712  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(28, RouteNumber);
6713  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
6714  //elements can continue to be removed from that route
6715  TTrackElement TE = Track->TrackElementAt(997, FirstRouteElementVecPos);
6716 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
6717  { //above condition removed v2.17.0 so non-facing signal or continuation doesn't stop route being removed
6718  //if it is a facing signal then it will be detected below and not removed
6719  bool FirstPass = true; //added at v2.8.0
6720  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
6721  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
6722  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(247, 0); //these will change at each element removal because OR is a reference to the real route
6723  int TVPos2 = PDE.GetTrackVectorPosition();
6724  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
6725  {
6726  break;
6727  }
6728  TTrackElement TE2 = Track->TrackElementAt(998, TVPos2);
6730  {
6731  AllRoutes->RemoveRouteElement(22, TE2.HLoc, TE2.VLoc, PDE.GetELink());
6732  }
6733  else
6734  {
6735  break;
6736  }
6737  FirstPass = false;
6738  }
6739  AllRoutes->RebuildRailwayFlag = true;
6740  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
6741  }
6742  }
6743  }
6744  Utilities->CallLogPop(1012);
6745 }
6746 
6747 // ---------------------------------------------------------------------------
6748 
6749 void TTrain::NewTrainService(int Caller, bool NoLogFlag) //, bool NoLogFlag added at v2.12.0 for new service tt skips
6750 // change to new train, give new service message
6751 //same RepeatNumber used for the new service
6752 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6753  TrainController->LogEvent("" + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6754  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6755 
6756 /* restriction removed at v2.19.0
6757  if(PowerAtRail < 1)
6758  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can form new service when power restored)
6759  {
6760  if(!ZeroPowerNoNewServiceMessage)
6761  {
6762  TrainController->StopTTClockMessage(87, HeadCode + ": A train without power can't form a new service");
6763  }
6764  ZeroPowerNoNewServiceMessage = true;
6765  Utilities->CallLogPop(2142);
6766  return;
6767  }
6768 */
6770 
6771  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6772 
6773  if(!NoLogFlag)
6774  {
6776  }
6777  UnplotTrain(3);
6780  StartSpeed = 0;
6785  HeadCode = NewHeadCode;
6787  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6788  {
6789  Description = OriginalDescription; //changed at v2.16.1 to train description
6790  }
6791  StoppedAtLocation = true;
6792  PlotStartPosition(5);
6794  // pale green
6797  TerminatedMessageSent = false;
6798 //Utilities->Pause(1000); //diagnostics
6799  Utilities->CallLogPop(1022);
6800 }
6801 
6802 // ---------------------------------------------------------------------------
6803 
6804 void TTrain::RemainHere(int Caller) //added warnings & reminders at v2.19.0 (not sent from LogAction as not called when train terminates)
6805 {
6806  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6807  if(RemainHereLogNotSent) // to prevent repeated logs
6808  {
6809  TrainController->LogEvent(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6810  RemainHereLogNotSent = false;
6811  }
6813  {
6814  TDateTime ActualTime = TrainController->TTClockTime;
6815  AnsiString BaseLog = "", Location = ActionVectorEntryPtr->LocationName;
6816  AnsiString PerfLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + " terminated at " + Location;
6818  {
6819  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + " terminated at " + Location;
6820  Display->WarningLog(25, PerfLog);
6821  PerfLogForm->PerformanceLog(65, BaseLog);
6822  }
6823  else if(ActionVectorEntryPtr->Reminder > 0)
6824  {
6825  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + " terminated at " + Location;
6826  Display->WarningLog(26, PerfLog);
6827  PerfLogForm->PerformanceLog(66, BaseLog);
6828  }
6829  else
6830  {
6831  PerfLogForm->PerformanceLog(67, PerfLog);
6832  }
6834  TerminatedMessageSent = true;
6835  }
6836  TimetableFinished = true;
6837  Utilities->CallLogPop(1023);
6838 }
6839 
6840 // ---------------------------------------------------------------------------
6841 
6842 void TTrain::SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
6843 /*
6844  Enter with pointer at next expected action, and IncNum the number by which have to increase the pointer
6845  to reach the action that is valid for the train's current position. i.e. IncNum error messages to be sent
6846  except where an action is a departure, starting at the current value for the pointer
6847  If IncNum is -1, then send messages for all remaining actions, including Fer if present
6848  If IncNum is -2, then send messages for all remaining actions, except Fer if present
6849 */{
6850  if((Ptr->Command == "Snt") && Ptr->SignallerControl)
6851  {
6852  return; // if remove train that starts under signaller control no messages needed
6853 
6854  }
6855  TrainController->LogEvent("" + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6856  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6857  if(IncNum > 0)
6858  {
6859  for(int x = 0; x < IncNum; x++)
6860  {
6861  if(x > 0)
6862  {
6863  Ptr++;
6864  }
6865  // arrival - no need to test for termination as this section only covers missed actions up to the
6866  // arrival point - may terminate later but that not missed
6867  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6868  {
6870  }
6871  // arrival & departure
6872  if(Ptr->FormatType == TimeTimeLoc)
6873  {
6875  }
6876  // departure
6877  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6878  {
6879  continue; // skip TimeLoc departures, message given for arrivals
6880  }
6881  // pass
6882  else if(Ptr->FormatType == PassTime)
6883  {
6885  }
6886  // split
6887  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6888  {
6890  }
6891  // jbo
6892  else if(Ptr->Command == "jbo")
6893  {
6895  }
6896  // dsc
6897  else if(Ptr->Command == "dsc") //new at v2.15.0
6898  {
6900  }
6901  //cms
6902  else if(Ptr->Command == "cms") //new at v2.21.0
6903  {
6905  }
6906  // Errors - have reached a station stop point (before a cdt) during Train->Update() so intervening actions can't
6907  // be starts, finishes or cdt
6908  else if((Ptr->Command == "Fns") || (Ptr->Command == "Frh") || (Ptr->Command == "Fer") || (Ptr->Command == "Fjo") || (Ptr->Command == "Snt") ||
6909  (Ptr->Command == "Sfs") || (Ptr->Command == "Snt-sh") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") || (Ptr->Command == "Sns-fsh") ||
6910  (Ptr->Command == "cdt") || (Ptr->Command == "Frh-sh") || (Ptr->Command == "Fns-sh") || (Ptr->Command == "F-nshs") ||
6911  (Ptr->FormatType == Repeat))
6912  {
6913  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6914  }
6915  }
6916  }
6917  else
6918  {
6919  bool IncludeFER = false;
6920  if(IncNum == -1)
6921  {
6922  IncludeFER = true;
6923  }
6924  while(true) // finish commands & repeats break out of loop
6925  {
6926  // Fer & excluded - send normal exit log to give minutes late or early - no, have already sent an unexpected exit message
6927  if(!IncludeFER && (Ptr->Command == "Fer"))
6928  {
6929  break;
6930  }
6931  // Fer & included
6932  else if(IncludeFER && (Ptr->Command == "Fer"))
6933  {
6935  break;
6936  }
6937  // Repeat
6938  else if(Ptr->FormatType == Repeat)
6939  {
6940  break;
6941  }
6942  // Fjo
6943  else if(Ptr->Command == "Fjo")
6944  {
6946  break;
6947  }
6948  // Frh
6949  else if(Ptr->Command == "Frh")
6950  {
6952  {
6954  TerminatedMessageSent = true;
6955  }
6956  break;
6957  }
6958  // Frh-sh
6959  else if(Ptr->Command == "Frh-sh")
6960  {
6962  {
6964  TerminatedMessageSent = true;
6965  }
6966  break;
6967  }
6968  // Fns, F-nshs, Fns-sh
6969  else if((Ptr->Command == "Fns") || (Ptr->Command == "F-nshs") || (Ptr->Command == "Fns-sh"))
6970  {
6972  break;
6973  }
6974  // end of breakout actions
6975  // arrival
6976  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6977  {
6978  if(IsTrainTerminating(1))
6979  {
6981  TerminatedMessageSent = true;
6982  }
6983  else
6984  {
6986  }
6987  }
6988  // arrival & departure
6989  else if(Ptr->FormatType == TimeTimeLoc)
6990  {
6992  }
6993  // departure
6994  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6995  {
6996  Ptr++;
6997  continue; // skip TimeLoc departures, message given for arrivals
6998  }
6999  // pass
7000  else if(Ptr->FormatType == PassTime)
7001  {
7003  }
7004  // split
7005  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
7006  {
7008  }
7009  // jbo
7010  else if(Ptr->Command == "jbo")
7011  {
7013  }
7014  // dsc
7015  else if(Ptr->Command == "dsc") //new at v2.15.0
7016  {
7017 // TrainController->LogActionError(65, HeadCode, "", FailMissedDSC, Ptr->LocationName); don't count as a missed event
7018  }
7019  // cms
7020  else if(Ptr->Command == "cms") //new at v2.21.0
7021  {
7022 // TrainController->LogActionError(, HeadCode, "", FailMissedCMS, Ptr->LocationName); don't count as a missed event
7023  }
7024  // cdt
7025  else if(Ptr->Command == "cdt")
7026  {
7027 // TrainController->LogActionError(25, HeadCode, "", FailMissedChangeDirection, Ptr->LocationName); //commented out at v2.12.0 as cdts not counted as missed events
7028  }
7029  // Errors
7030  else if((Ptr->Command == "Snt-sh") || (Ptr->Command == "Sfs") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") ||
7031  (Ptr->Command == "Sns-fsh") || ((Ptr->Command == "Snt") && !Ptr->SignallerControl))
7032  {
7033  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
7034  }
7035  Ptr++;
7036  }
7037  TimetableFinished = true;
7038  }
7039  Utilities->CallLogPop(1021);
7040 }
7041 
7042 // ---------------------------------------------------------------------------
7043 
7044 bool TTrain::TrainToJoinIsAdjacent(int Caller, TTrain* &TrainToJoin)
7045 // ensure same repeatnumber
7046 {
7047  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToJoinIsAdjacent" + "," + HeadCode);
7049 
7050  if(TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
7051  {
7052  Utilities->CallLogPop(1024);
7053  return(false);
7054  }
7055  TrainToJoin = &(TrainController->TrainVectorAtIdent(33, TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
7056  if(TrainToJoin->StoppedAtLocation && (TrainToJoin->TrainMode == Timetable) && (TrainToJoin->ActionVectorEntryPtr->Command == "jbo"))
7057  {
7058  if((Track->TrackElementAt(610, LeadElement).Conn[LeadExitPos] == TrainToJoin->LeadElement) || (Track->TrackElementAt(611,
7059  LeadElement).Conn[LeadExitPos] == TrainToJoin->MidElement) || (Track->TrackElementAt(612, MidElement).Conn[MidEntryPos] == TrainToJoin->LeadElement)
7060  || (Track->TrackElementAt(613, MidElement).Conn[MidEntryPos] == TrainToJoin->MidElement))
7061  {
7062  Utilities->CallLogPop(1025);
7063  return(true);
7064  }
7065  }
7066  Utilities->CallLogPop(1026);
7067  return(false);
7068 }
7069 
7070 // ---------------------------------------------------------------------------
7071 
7072 bool TTrain::TrainToBeJoinedByIsAdjacent(int Caller, TTrain* &TrainToBeJoinedBy)
7073 // ensure same repeatnumber
7074 {
7075  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToBeJoinedByIsAdjacent" + "," + HeadCode);
7076  TTrainDataEntry *TrainToBeJoinedByTDEntry = ActionVectorEntryPtr->LinkedTrainEntryPtr;
7077 
7078  if(TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
7079  {
7080  Utilities->CallLogPop(1027);
7081  return(false);
7082  }
7083  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(15, TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
7084  if(TrainToBeJoinedBy->StoppedAtLocation && (TrainToBeJoinedBy->TrainMode == Timetable) && (TrainToBeJoinedBy->ActionVectorEntryPtr->Command == "Fjo"))
7085  {
7086  if((Track->TrackElementAt(614, LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(615,
7087  LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->MidElement) || (Track->TrackElementAt(616,
7088  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(617,
7089  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->MidElement))
7090  {
7091  Utilities->CallLogPop(1028);
7092  return(true);
7093  }
7094  }
7095  Utilities->CallLogPop(1029);
7096  return(false);
7097 }
7098 
7099 // ---------------------------------------------------------------------------
7100 
7101 void TTrain::NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7102 { //same RepeatNumber (i.e. 0) used for the new shuttle
7103 //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7104  TrainController->LogEvent("" + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7105  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7106 
7107 /* restriction removed at v2.19.0
7108  if(PowerAtRail < 1)
7109  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7110  {
7111  if(!ZeroPowerNoNewShuttleFromNonRepeatMessage)
7112  {
7113  TrainController->StopTTClockMessage(88, HeadCode + ": A train without power can't form a new service");
7114  }
7115  ZeroPowerNoNewShuttleFromNonRepeatMessage = true;
7116  Utilities->CallLogPop(2143);
7117  return;
7118  }
7119 */
7120  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7121  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7122 
7123  if(!NoLogFlag)
7124  {
7126  }
7127  UnplotTrain(4);
7130  StartSpeed = 0;
7135  HeadCode = NewHeadCode;
7137  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7138  {
7139  Description = OriginalDescription; //changed at v2.16.1 to train description
7140  }
7141  IncrementalMinutes = TrainDataEntryPtr->ActionVector.back().RearStartOrRepeatMins;
7142  IncrementalDigits = TrainDataEntryPtr->ActionVector.back().FrontStartOrRepeatDigits;
7143  StoppedAtLocation = true;
7144  PlotStartPosition(6);
7146  // pale green
7149  TerminatedMessageSent = false;
7150  Utilities->CallLogPop(1078);
7151 }
7152 
7153 // ---------------------------------------------------------------------------
7154 
7155 void TTrain::RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7156 // need to check whether all repeats finished or not
7157 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7158  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7159  if(RemainHereLogNotSent) // to prevent repeated logs
7160  {
7161  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7162  RemainHereLogNotSent = false;
7163  }
7165  // finished all repeats
7166  {
7168  { //no need to suppress this LogAction because BecomeNewService won't be available in this case
7171  TerminatedMessageSent = true;
7172  // no need to clear message as no more actions
7173  }
7174  TimetableFinished = true;
7175  Utilities->CallLogPop(1080);
7176  return;
7177  }
7178 
7179 /* restriction removed at v2.19.0
7180  if(PowerAtRail < 1)
7181  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7182  {
7183  if(!ZeroPowerNoRepeatShuttleMessage)
7184  {
7185  TrainController->StopTTClockMessage(89, HeadCode + ": A train without power can't form a new service");
7186  }
7187  ZeroPowerNoRepeatShuttleMessage = true;
7188  Utilities->CallLogPop(2144);
7189  return;
7190  }
7191 */
7192  int TempRepeatNumber = RepeatNumber + 1;
7193  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7194  // until after LogAction or the wrong time will be used
7195  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(6, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7196  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7197 
7198  if(!NoLogFlag)
7199  {
7201  }
7202  RepeatNumber++;
7203  UnplotTrain(5);
7206  StartSpeed = 0;
7211  HeadCode = NewHeadCode;
7213  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7214  {
7215  Description = OriginalDescription; //changed at v2.16.1 to train description
7216  }
7217  StoppedAtLocation = true;
7218  PlotStartPosition(7);
7220  // pale green
7223  TerminatedMessageSent = false;
7224  Utilities->CallLogPop(1079);
7225 }
7226 
7227 // ---------------------------------------------------------------------------
7228 
7229 void TTrain::RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips
7230 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7231  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7232  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7233 
7234 /* restriction removed at v2.19.0
7235  if(PowerAtRail < 1)
7236  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7237  {
7238  if(!ZeroPowerNoRepeatShuttleOrNewServiceMessage)
7239  {
7240  TrainController->StopTTClockMessage(90, HeadCode + ": A train without power can't form a new service");
7241  }
7242  ZeroPowerNoRepeatShuttleOrNewServiceMessage = true;
7243  Utilities->CallLogPop(2145);
7244  return;
7245  }
7246 */
7248  // finished all repeats
7249  {
7250  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7251  if(!NoLogFlag)
7252  {
7254  }
7255  RepeatNumber = 0;
7256  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7257  UnplotTrain(6);
7260  StartSpeed = 0;
7262  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).TrainID = TrainID; // but note that RepeatNumber = 0
7263  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).RunningEntry = Running; // but note that RepeatNumber = 0
7265  HeadCode = NewHeadCode;
7267  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7268  {
7269  Description = OriginalDescription; //changed at v2.16.1 to train description
7270  }
7271  StoppedAtLocation = true;
7272  PlotStartPosition(9);
7276  TerminatedMessageSent = false;
7277  Utilities->CallLogPop(1081);
7278  return;
7279  }
7280  int TempRepeatNumber = RepeatNumber + 1;
7281  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7282  // until after LogAction or the wrong time will be used
7283  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(7, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7284  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7285 
7286  if(!NoLogFlag)
7287  {
7289  }
7290  RepeatNumber++;
7291  UnplotTrain(7);
7294  StartSpeed = 0;
7299  HeadCode = NewHeadCode;
7301  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7302  {
7303  Description = OriginalDescription; //changed at v2.16.1 to train description
7304  }
7305  StoppedAtLocation = true;
7306  PlotStartPosition(8);
7308  // pale green
7311  TerminatedMessageSent = false;
7312  Utilities->CallLogPop(1082);
7313 }
7314 
7315 // ---------------------------------------------------------------------------
7316 
7318 {
7319  // Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
7320  // entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
7321  // must be preceded by a TimeLoc departure
7322  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainTerminating" + "," + HeadCode);
7323  for(unsigned int x = 1; x < TrainDataEntryPtr->ActionVector.size(); x++)
7324  {
7326  {
7327  if(((ActionVectorEntryPtr + x)->Command == "Fer") || ((ActionVectorEntryPtr + x)->FormatType == TimeLoc))
7328  {
7329  Utilities->CallLogPop(1083);
7330  return(false);
7331  }
7332  else if((ActionVectorEntryPtr + x)->SequenceType == FinishSequence)
7333  {
7334  Utilities->CallLogPop(1084);
7335  return(true);
7336  }
7337  }
7338  }
7339  Utilities->CallLogPop(1085);
7340  return(false);
7341 }
7342 
7343 // ---------------------------------------------------------------------------
7344 
7345 bool TTrain::AbleToMove(int Caller)
7346 {
7347  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMove" + "," + HeadCode);
7348  bool Able = true;
7349 
7350  if(Crashed || Derailed || StoppedAtBuffers || StoppedAtSignal || StoppedWithoutPower) // StoppedWithoutPower added at v2.4.0
7351  {
7352  // StoppedForTrainInFront removed as tested below
7353  Utilities->CallLogPop(2146); // added v2.4.0
7354  return(false); // added v2.4.0
7355  }
7356  if(LeadElement > -1)
7357  {
7358  if(Track->TrackElementAt(801, LeadElement).TrackType == Buffers) //moved up here from 'else' below at v2.12.0
7359  {
7360  StoppedForTrainInFront = false;
7361  TrainInFront = false;
7362  // don't set StoppedAtBuffers as (presumably) StoppedAtLocation & leave it at that
7363  Utilities->CallLogPop(2456);
7364  return(false);
7365  }
7366  int FrontPos = Track->TrackElementAt(678, LeadElement).Conn[LeadExitPos];
7367  int FrontEntryPos = Track->TrackElementAt(679, LeadElement).ConnLinkPos[LeadExitPos];
7368  if((FrontPos > -1) && (TrainMode == Signaller) && TrainInFront) //check if train in front still there
7369  {
7370  TTrackElement TrackElement = Track->TrackElementAt(680, FrontPos);
7371  if((TrackElement.TrackType != Bridge) && (TrackElement.TrainIDOnElement == -1))
7372  {
7373  Able = true;
7374  TrainInFront = false;
7375  }
7376  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos < 2) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 == -1))
7377  {
7378  Able = true;
7379  TrainInFront = false;
7380  }
7381  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos > 1) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 == -1))
7382  {
7383  Able = true;
7384  TrainInFront = false;
7385  }
7386  else
7387  {
7388  Able = false; //added at v2.12.0 as train still in front so don't want signaller popup options to move available
7389  }
7390  }
7391  }
7392  else // leaving at a continuation so keep going
7393  {
7394  Able = true;
7395  StoppedForTrainInFront = false;
7396  TrainInFront = false;
7397  }
7398  Utilities->CallLogPop(1454);
7399  return(Able);
7400 }
7401 
7402 // ---------------------------------------------------------------------------
7403 
7405 {
7406  // first check if a train immediately in front (may have moved there since this train stopped so StoppedForTrainInFront
7407  // won't be set; if there is a train then set StoppedForTrainInFront
7408  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMoveButForSignalOrTrainInFront" + "," + HeadCode);
7409  // addition below for v1.3.2 after Carwyn Thomas fault reported 24/05/15 - need to check if exiting at continuation (LeadElement == -1) as if so fails at VecPos = .....
7410  if(LeadElement == -1) // exiting at continuation
7411  {
7412  Utilities->CallLogPop(2045);
7413  return(false);
7414  }
7415  // end of addition
7416  int VecPos = Track->TrackElementAt(654, LeadElement).Conn[LeadExitPos];
7417  int NextEntryPos = Track->TrackElementAt(655, LeadElement).ConnLinkPos[LeadExitPos];
7418 
7419  if(Track->OtherTrainOnTrack(5, VecPos, NextEntryPos, TrainID))
7420  {
7421  TrainInFront = true;
7422  Utilities->CallLogPop(1455);
7423  return(false);
7424  }
7425  else
7426  {
7427  Utilities->CallLogPop(1456);
7429  // StoppedWithoutPower added v2.4.0
7430  }
7431 }
7432 
7433 // ---------------------------------------------------------------------------
7434 
7436 {
7437  // unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd
7438  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SignallerChangeTrainDirection" + "," + HeadCode);
7439  TColor TempColour = BackgroundColour;
7440 
7441  UnplotTrain(8);
7444  StartSpeed = 0;
7445  PlotStartPosition(2);
7446  PlotTrainWithNewBackgroundColour(26, TempColour, Display);
7447 
7448  //now erase a stub route if there is one, added at v2.5.1
7449  //first element of route is now immediately behind the train (i.e. next to MidElement)
7450  if(MidEntryPos >= 0)
7451  {
7452  TTrackElement MidTrackElement = Track->TrackElementAt(1000, MidElement);
7453  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
7454  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
7455  int RouteNumber = -1;
7456  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(35, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
7457  if(RouteType == TAllRoutes::NotAutoSigsRoute)
7458  {
7459  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(29, RouteNumber);
7460  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
7461  //elements can continue to be removed from that route
7462  TTrackElement TE = Track->TrackElementAt(1001, FirstRouteElementVecPos);
7463 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
7464  { //above condition removed at v2.17.0 so non-facing signal or continuation doesn't stop route being removed
7465  //if it is a facing signal then it will be detected below and not removed
7466  bool FirstPass = true; //added at v2.8.0
7467  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
7468  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
7469  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(248, 0); //these will change at each element removal because OR is a reference to the real route
7470  int TVPos2 = PDE.GetTrackVectorPosition();
7471  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
7472  {
7473  break;
7474  }
7475  TTrackElement TE2 = Track->TrackElementAt(1002, TVPos2);
7477  {
7478  AllRoutes->RemoveRouteElement(23, TE2.HLoc, TE2.VLoc, PDE.GetELink());
7479  }
7480  else
7481  {
7482  break;
7483  }
7484  FirstPass = false;
7485  }
7486  AllRoutes->RebuildRailwayFlag = true;
7487  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
7488  }
7489  }
7490  }
7491  Utilities->CallLogPop(1102);
7492 }
7493 
7494 // ---------------------------------------------------------------------------
7495 
7497 {
7498  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7499  ",FloatingLabelNextString" + "," + HeadCode);
7500  AnsiString RetStr = "", LocationName = "";
7501  //record action time - may be arrival, departure or event for use later (added at v2.13.2)
7502  TDateTime ActionTime = Ptr->ArrivalTime;
7503  if(ActionTime == TDateTime(-1))
7504  {
7505  ActionTime = Ptr->DepartureTime;
7506  }
7507  if(ActionTime == TDateTime(-1))
7508  {
7509  ActionTime = Ptr->EventTime;
7510  }
7511  //If ActionTime still TDateTime(-1) then the train has terminated and 'None...' will be returned
7512  //Now correct it for repeats
7513  if(ActionTime != TDateTime(-1))
7514  {
7515  ActionTime = GetTrainTime(64, ActionTime);
7516  }
7517  if(int(DelayedRandMins) > 0)
7518  {
7519  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7520  {
7521  throw Exception("Error - start entry in FloatingLabelNextString");
7522  }
7523  if(Ptr->FormatType == TimeTimeLoc)
7524  {
7525  if(TrainMode == Timetable)
7526  {
7527  if(!TrainAtLocation(0, LocationName) || (LocationName != Ptr->LocationName))
7528  // not arrived yet in tt mode
7529  {
7530  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(2, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7531  }
7532  else
7533  {
7534  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7535  }
7536  }
7537  else // TrainMode == Signaller
7538  {
7539  if(!DepartureTimeSet) // not arrived yet
7540  {
7541  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(45, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7542  }
7543  else
7544  {
7545  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7546  }
7547  }
7548  }
7549  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7550  {
7551  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(4, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7552  }
7553  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7554  {
7555  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7556  }
7557  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7558  {
7559  RetStr = "Depart " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(46, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7560  }
7561  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7562  {
7563  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(31, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7564  }
7565  else if(Ptr->Command == "Fns")
7566  {
7567  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(8, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7568  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(6, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7569  RetStr = GetNewServiceDepartureInfo(0, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7570  }
7571  else if(Ptr->Command == "F-nshs")
7572  {
7573  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;// + " at approx. " +
7574  Utilities->Format96HHMM(GetTrainTime(32, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7575  RetStr = GetNewServiceDepartureInfo(1, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7576  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7577  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7578  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7579  }
7580  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7581  {
7582  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(9, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7583  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(7, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7584  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7585  RetStr = GetNewServiceDepartureInfo(2, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7586  }
7587  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7588  {
7589  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7590  +" at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(8, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7591  RetStr = GetNewServiceDepartureInfo(3, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7592  }
7593  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7594  {
7595  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(10, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7596  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(9, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7597  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7598  RetStr = GetNewServiceDepartureInfo(4, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7599  }
7600  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7601  {
7602  RetStr ="None, train terminated at " + Ptr->LocationName;
7603  }
7604  else if(Ptr->Command == "Frh")
7605  {
7606  RetStr = "None, train terminated at " + Ptr->LocationName;
7607  }
7608  else if(Ptr->Command == "Fer")
7609  {
7610  AnsiString AllowedExits = "";
7611  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(1, Ptr->ExitList, AllowedExits) + AllowedExits;
7612  }
7613  else if(Ptr->Command == "Fjo")
7614  {
7615  RetStr = "Join " + TrainController->GetRepeatHeadCode(11, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7616 // Utilities->Format96HHMM(GetTrainTime(11, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7617  }
7618  else if(Ptr->Command == "jbo")
7619  {
7620  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(12, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7621 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(12, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7622  }
7623  else if(Ptr->Command == "fsp")
7624  {
7625  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(13, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7626 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(13, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7627  }
7628  else if(Ptr->Command == "rsp")
7629  {
7630  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(14, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7631 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(14, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7632  }
7633  else if(Ptr->Command == "cdt")
7634  {
7635  RetStr = "Change direction at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(15, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7636  }
7637  else if(Ptr->Command == "dsc")
7638  {
7639  RetStr = "Change description at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(65, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7640  }
7641  else if(Ptr->Command == "cms")
7642  {
7643  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(75, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7644  }
7645  }
7646  else if(TrainController->TTClockTime > ActionTime) //condition added at v2.13.2 for trains that are delayed other than suffering a random delay
7647  {
7648  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7649  {
7650  throw Exception("Error - start entry in FloatingLabelNextString where TTClockTime > ActionTime");
7651  }
7652  if(Ptr->FormatType == TimeTimeLoc)
7653  {
7654  if(TrainMode == Timetable)
7655  {
7656  if(!TrainAtLocation(4, LocationName) || (LocationName != Ptr->LocationName))
7657  // not arrived yet in tt mode
7658  {
7659  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7660  }
7661  else
7662  {
7663  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7664  }
7665  }
7666  else // TrainMode == Signaller
7667  {
7668  if(!DepartureTimeSet) // not arrived yet
7669  {
7670  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7671  }
7672  else
7673  {
7674  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7675  }
7676  }
7677  }
7678  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7679  {
7680  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7681  }
7682  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7683  {
7684  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7685  }
7686  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7687  {
7688  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7689  }
7690  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7691  {
7692  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7693  }
7694  else if(Ptr->Command == "Fns")
7695  {
7696  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(60, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7697  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7698  RetStr = GetNewServiceDepartureInfo(19, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7699  }
7700  else if(Ptr->Command == "F-nshs")
7701  {
7702  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7704  RetStr = GetNewServiceDepartureInfo(20, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7705  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7706  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7707  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7708  }
7709  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7710  {
7711  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(61, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7712  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7713  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7714  RetStr = GetNewServiceDepartureInfo(21, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7715  }
7716  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7717  {
7718  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7719  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7720  RetStr = GetNewServiceDepartureInfo(22, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7721  }
7722  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7723  {
7724  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(62, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7725  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7726  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7727  RetStr = GetNewServiceDepartureInfo(23, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7728  }
7729  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7730  {
7731  RetStr ="None, train terminated at " + Ptr->LocationName;
7732  }
7733  else if(Ptr->Command == "Frh")
7734  {
7735  RetStr = "None, train terminated at " + Ptr->LocationName;
7736  }
7737  else if(Ptr->Command == "Fer")
7738  {
7739  AnsiString AllowedExits = "";
7740  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(5, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime)*/ + AllowedExits;
7741  }
7742  else if(Ptr->Command == "Fjo")
7743  {
7744  RetStr = "Join " + TrainController->GetRepeatHeadCode(63, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7746  }
7747  else if(Ptr->Command == "jbo")
7748  {
7749  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(64, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7750  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7751  }
7752  else if(Ptr->Command == "fsp")
7753  {
7754  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(65, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7755  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7756  }
7757  else if(Ptr->Command == "rsp")
7758  {
7759  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(66, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7760  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7761  }
7762  else if(Ptr->Command == "cdt")
7763  {
7764  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7765  }
7766  else if(Ptr->Command == "dsc")
7767  {
7768  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7769  }
7770  else if(Ptr->Command == "cms")
7771  {
7772  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7773  }
7774  }
7775  else //train not delayed
7776  {
7777  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7778  {
7779  throw Exception("Error - start entry in FloatingLabelNextString in final 'else'");
7780  }
7781  if(Ptr->FormatType == TimeTimeLoc)
7782  {
7783  if(TrainMode == Timetable)
7784  {
7785  if(!TrainAtLocation(3, LocationName) || (LocationName != Ptr->LocationName))
7786  // not arrived yet in tt mode
7787  {
7788  RetStr = "Arrive " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(48, Ptr->ArrivalTime));
7789  }
7790  else
7791  {
7792  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7793  }
7794  }
7795  else // TrainMode == Signaller
7796  {
7797  if(!DepartureTimeSet) // not arrived yet
7798  {
7799  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(49, Ptr->ArrivalTime));
7800  }
7801  else
7802  {
7803  RetStr = "Depart " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7804  }
7805  }
7806  }
7807  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7808  {
7809  RetStr = "Arrive " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(50, Ptr->ArrivalTime));
7810  }
7811  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7812  {
7813  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7814  }
7815  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7816  {
7817  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7818  }
7819  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7820  {
7821  RetStr = "Pass " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(52, Ptr->EventTime));
7822  }
7823  else if(Ptr->Command == "Fns")
7824  {
7825  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(53, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7826  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(53, Ptr->EventTime));
7827  RetStr = GetNewServiceDepartureInfo(10, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7828  }
7829  else if(Ptr->Command == "F-nshs")
7830  {
7831  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7833  RetStr = GetNewServiceDepartureInfo(12, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7834  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7835  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7836  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7837  }
7838  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7839  {
7840  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(54, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7841  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(55, Ptr->EventTime));
7842  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7843  RetStr = GetNewServiceDepartureInfo(14, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7844  }
7845  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7846  {
7847  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7848  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(56, Ptr->EventTime));
7849  RetStr = GetNewServiceDepartureInfo(16, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7850  }
7851  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7852  {
7853  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(55, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7854  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(57, Ptr->EventTime));
7855  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7856  RetStr = GetNewServiceDepartureInfo(18, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7857  }
7858  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7859  {
7860  RetStr ="None, train terminated at " + Ptr->LocationName;
7861  }
7862  else if(Ptr->Command == "Frh")
7863  {
7864  RetStr = "None, train terminated at " + Ptr->LocationName;
7865  }
7866  else if(Ptr->Command == "Fer")
7867  {
7868  AnsiString AllowedExits = "";
7869  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(4, Ptr->ExitList, AllowedExits) + " at approx. " + Utilities->Format96HHMM(GetTrainTime(62, Ptr->EventTime)) + AllowedExits;
7870  }
7871  else if(Ptr->Command == "Fjo")
7872  {
7873  RetStr = "Join " + TrainController->GetRepeatHeadCode(56, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7875  }
7876  else if(Ptr->Command == "jbo")
7877  {
7878  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(57, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7879  " at approx. " + Utilities->Format96HHMM(GetTrainTime(59, Ptr->EventTime));
7880  }
7881  else if(Ptr->Command == "fsp")
7882  {
7883  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(58, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7884  " at approx. " + Utilities->Format96HHMM(GetTrainTime(60, Ptr->EventTime));
7885  }
7886  else if(Ptr->Command == "rsp")
7887  {
7888  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(59, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7889  " at approx. " + Utilities->Format96HHMM(GetTrainTime(61, Ptr->EventTime));
7890  }
7891  else if(Ptr->Command == "cdt")
7892  {
7893  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(63, Ptr->EventTime));
7894  }
7895  else if(Ptr->Command == "dsc")
7896  {
7897  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(66, Ptr->EventTime));
7898  }
7899  else if(Ptr->Command == "cms")
7900  {
7901  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(76, Ptr->EventTime));
7902  }
7903  }
7904  Utilities->CallLogPop(1124);
7905  return(RetStr);
7906 }
7907 
7908 // ---------------------------------------------------------------------------
7909 /* as was
7910 AnsiString TTrain::FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
7911 {
7912  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7913  ",FloatingLabelNextString" + "," + HeadCode);
7914  AnsiString RetStr = "", LocationName = "";
7915 
7916  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7917  {
7918  throw Exception("Error - start entry in FloatingLabelNextString");
7919  }
7920  if(Ptr->FormatType == TimeTimeLoc)
7921  {
7922  if(TrainMode == Timetable)
7923  {
7924  if(!TrainAtLocation(, LocationName) || (LocationName != Ptr->LocationName))
7925  // not arrived yet in tt mode
7926  {
7927  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7928  }
7929  else
7930  {
7931  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7932  }
7933  }
7934  else // TrainMode == Signaller
7935  {
7936  if(!DepartureTimeSet) // not arrived yet
7937  {
7938  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7939  }
7940  else
7941  {
7942  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7943  }
7944  }
7945  }
7946  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7947  {
7948  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7949  }
7950  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7951  {
7952  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7953  }
7954  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7955  {
7956  RetStr = "Depart " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7957  }
7958  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7959  {
7960  RetStr = "Pass " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7961  }
7962  else if(Ptr->Command == "Fns")
7963  {
7964  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7965  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7966  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7967  }
7968  else if(Ptr->Command == "F-nshs")
7969  {
7970  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at " +
7971  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7972  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7973  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7974  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7975  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7976  }
7977  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7978  {
7979  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7980  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7981  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7982  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7983  }
7984  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7985  {
7986  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7987  +" at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7988  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7989  }
7990  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7991  {
7992  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7993  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7994  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7995  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7996  }
7997  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7998  {
7999  RetStr ="None, train terminated at " + Ptr->LocationName;
8000  }
8001  else if(Ptr->Command == "Frh")
8002  {
8003  RetStr = "None, train terminated at " + Ptr->LocationName;
8004  }
8005  else if(Ptr->Command == "Fer")
8006  {
8007  AnsiString AllowedExits = "";
8008  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(, Ptr->ExitList, AllowedExits) + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime)) + AllowedExits;
8009  }
8010  else if(Ptr->Command == "Fjo")
8011  {
8012  RetStr = "Join " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at " +
8013  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8014  }
8015  else if(Ptr->Command == "jbo")
8016  {
8017  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8018  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8019  }
8020  else if(Ptr->Command == "fsp")
8021  {
8022  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8023  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8024  }
8025  else if(Ptr->Command == "rsp")
8026  {
8027  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8028  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8029  }
8030  else if(Ptr->Command == "cdt")
8031  {
8032  RetStr = "Change direction at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8033  }
8034  Utilities->CallLogPop();
8035  return(RetStr);
8036 }
8037 */
8038 // ---------------------------------------------------------------------------
8039 
8040 AnsiString TTrain::GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
8041 { //last bool added at v2.13.2 so departure info adds random delay if actual rather than not timetable time required
8042  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) + ","
8043  + AnsiString(RptNum) + ",GetNewServiceDepartureInfo," + HeadCode);
8044  AnsiString DepTime = "", EventTime = "";
8045  bool CDTFlag = false; //reports if train changes direction before departs
8046  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
8047  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
8048  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
8049  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
8050  {
8051  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
8052  {
8053  TowardsLocation = AVI->LocationName;
8054  }
8055  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
8056  {
8057  TTrackElement TE = Track->TrackElementAt(1452, (AVI->ExitList.front()));
8058  if(TE.ActiveTrackElementName != "")
8059  {
8060  TowardsLocation = TE.ActiveTrackElementName;
8061  }
8062  else
8063  {
8064  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
8065  }
8066  }
8067  }
8068 
8069  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
8070  {
8071  if(AVI->Command == "cdt")
8072  {
8073  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
8074  continue;
8075  }
8076  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
8077  {
8078  TDateTime TTTime = TrainController->GetControllerTrainTime(19, AVI->EventTime, RptNum, IncrementalMinutes);
8079  if((DelayedRandMins >= 1) && !TimetableTime)
8080  {
8081  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8082  }
8083  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8084  {
8086  }
8087  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8088  {
8089  EventTime = Utilities->Format96HHMM(TTTime);
8090  }
8091  RetStr += "\nNew service splits at approx. " + EventTime;
8092  Utilities->CallLogPop(2234);
8093  return(RetStr);
8094  }
8095  if(AVI->Command == "jbo") //added at v2.15.0
8096  {
8097  TDateTime TTTime = TrainController->GetControllerTrainTime(28, AVI->EventTime, RptNum, IncrementalMinutes);
8098  if((DelayedRandMins >= 1) && !TimetableTime)
8099  {
8100  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8101  }
8102  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8103  {
8105  }
8106  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8107  {
8108  EventTime = Utilities->Format96HHMM(TTTime);
8109  }
8110  RetStr += "\nNew service joined by " + TrainController->GetRepeatHeadCode(68, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8111  Utilities->CallLogPop(2595); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8112  return(RetStr);
8113  }
8114  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh")) //added at v2.15.0
8115  {
8116  TDateTime TTTime = TrainController->GetControllerTrainTime(29, AVI->EventTime, RptNum, IncrementalMinutes);
8117  if((DelayedRandMins >= 1) && !TimetableTime)
8118  {
8119  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8120  }
8121  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8122  {
8124  }
8125  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8126  {
8127  EventTime = Utilities->Format96HHMM(TTTime);
8128  }
8129  RetStr += "\nNew service finishes and forms another new service at approx. " + EventTime;
8130  Utilities->CallLogPop(2615);
8131  return(RetStr);
8132  }
8133  if(AVI->Command == "Fjo") //added at v2.15.0
8134  {
8135  TDateTime TTTime = TrainController->GetControllerTrainTime(26, AVI->EventTime, RptNum, IncrementalMinutes);
8136  if((DelayedRandMins >= 1) && !TimetableTime)
8137  {
8138  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8139  }
8140  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8141  {
8143  }
8144  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8145  {
8146  EventTime = Utilities->Format96HHMM(TTTime);
8147  }
8148  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(69, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8149  Utilities->CallLogPop(2605); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8150  return(RetStr);
8151  }
8152  if(AVI->Command == "Frh") //added at v2.15.0
8153  {
8154  RetStr += "\nNew service finishes and remains at the location.";
8155  Utilities->CallLogPop(2606);
8156  return(RetStr);
8157  }
8158  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
8159  {
8160  if(TimetableTime) //don't add random delay
8161  {
8162  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(17, AVI->DepartureTime, RptNum, IncrementalMinutes));
8163  if(CDTFlag)
8164  {
8165  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8166  {
8167  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
8168  }
8169  else
8170  {
8171  RetStr += "\nNew service changes direction then departs at " + DepTime;
8172  }
8173  }
8174  else
8175  {
8176  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8177  {
8178  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
8179  }
8180  else
8181  {
8182  RetStr += "\nNew service departs at " + DepTime;
8183  }
8184  }
8185  }
8186  else if(DelayedRandMins >= 1)//add random delay
8187  {
8188  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(24, AVI->DepartureTime + TDateTime(DelayedRandMins/1440), RptNum, IncrementalMinutes));
8189  if(CDTFlag)
8190  {
8191  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8192  {
8193  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8194  }
8195  else
8196  {
8197  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8198  }
8199  }
8200  else
8201  {
8202  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8203  {
8204  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8205  }
8206  else
8207  {
8208  RetStr += "\nNew service departs at approx. " + DepTime;
8209  }
8210  }
8211  }
8212  else //no random delay but may be delayed for other reasons
8213  {
8214  TDateTime TTTime = TrainController->GetControllerTrainTime(25, AVI->DepartureTime, RptNum, IncrementalMinutes);
8215  if(TrainController->TTClockTime > TTTime)
8216  {
8218  }
8219  else
8220  {
8221  DepTime = Utilities->Format96HHMM(TTTime);
8222  }
8223  if(CDTFlag)
8224  {
8225  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8226  {
8227  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8228  }
8229  else
8230  {
8231  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8232  }
8233  }
8234  else
8235  {
8236  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8237  {
8238  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8239  }
8240  else
8241  {
8242  RetStr += "\nNew service departs at approx. " + DepTime;
8243  }
8244  }
8245  }
8246  Utilities->CallLogPop(2236);
8247  return(RetStr);
8248  }
8249  }
8250  Utilities->CallLogPop(2208);
8251  return(RetStr); //if reach here then RetStr doesn't change
8252 }
8253 
8254 // ---------------------------------------------------------------------------
8255 
8257 // Enter with Ptr pointing to first action to be listed (i.e. next action)
8258 // If there are actions to be skipped but a departure is awaited (SkippedDeparture = true) then after the departure Ptr moves forward by SkipPtrValue
8259 {
8260  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
8261  ",FloatingTimetableString" + "," + HeadCode);
8262  AnsiString RetStr = "", PartStr = "";
8263  int Count = 0;
8264  bool SkipDep = false, SkipDepActedOn = false; //SkipDepActedOn ensures only one SkipDep acted on
8265  AnsiString LocName = Ptr->LocationName;
8266  AnsiString MinMinsString = "";
8267 
8268  if((Ptr->Command != "") && (Ptr->Command[1] == 'S') && (TrainMode == Timetable))
8269  // can start in signaller control so exclude this
8270  {
8271  throw Exception("Error - start entry in FloatingTimetableString");
8272  }
8273  TActionVectorEntry *EntryPtr = Ptr; //used in TimeTimeLoc check later to distinguish between starting at the location and later same location entries
8274  bool FirstPass = true; // different first TimeTimeLoc display if in signaller control
8275  Ptr--; // because incremented at start of loop
8276 
8277  do
8278  {
8279  Ptr++;
8280  AnsiString TrainLoc = ""; //moved to here from below at v2.20.1 when added last 2 conditions in first PassTime check below (see Albie Vowles' email of 25/07/24)
8281  if((Ptr->FormatType == Repeat) || TimetableFinished)
8282  {
8283  break;
8284  }
8285  if((Ptr->FormatType == TimeTimeLoc) && FirstPass)
8286  {
8287 // AnsiString TrainLoc = ""; moved from here at v2.20.1 - see above
8288  if(TrainMode == Timetable)
8289  {
8290  if(TrainAtLocation(1, TrainLoc) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8291  {
8292  PartStr = Utilities->Format96HHMM(GetTrainTime(33, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName; //no MDT for departure
8293  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8294  {
8295  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8296  }
8297  }
8298  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8299  {
8300  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8301  {
8302  double MDTdouble = Ptr->MinDwellTime / 60;
8303  double MDT = int(MDTdouble * 10);
8304  MDT = MDT / 10;
8305  MinMinsString = "mins";
8306  if((MDT < 1.1) && (MDT > 0.9))
8307  {
8308  MinMinsString = "min";
8309  }
8310  PartStr = Utilities->Format96HHMM(GetTrainTime(34, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8311  }
8312  else
8313  {
8314  PartStr = Utilities->Format96HHMM(GetTrainTime(89, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8315  }
8316  }
8317  else
8318  {
8319  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8320  {
8321  double MDTdouble = Ptr->MinDwellTime / 60;
8322  double MDT = int(MDTdouble * 10);
8323  MDT = MDT / 10;
8324  MinMinsString = "mins";
8325  if((MDT < 1.1) && (MDT > 0.9))
8326  {
8327  MinMinsString = "min";
8328  }
8329  PartStr = Utilities->Format96HHMM(GetTrainTime(16, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8330  Utilities->Format96HHMM(GetTrainTime(17, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8331  }
8332  else
8333  {
8334  PartStr = Utilities->Format96HHMM(GetTrainTime(80, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8335  Utilities->Format96HHMM(GetTrainTime(84, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8336  }
8337  Count++; // because there are 2 entries
8338  }
8339  }
8340  else // TrainMode == Signaller
8341  {
8342  if(DepartureTimeSet)
8343  {
8344  PartStr = Utilities->Format96HHMM(GetTrainTime(37, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8345  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8346  {
8347  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8348  }
8349  }
8350  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8351  {
8352  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8353  {
8354  double MDTdouble = Ptr->MinDwellTime / 60;
8355  double MDT = int(MDTdouble * 10);
8356  MDT = MDT / 10;
8357  MinMinsString = "mins";
8358  if((MDT < 1.1) && (MDT > 0.9))
8359  {
8360  MinMinsString = "min";
8361  }
8362  PartStr = Utilities->Format96HHMM(GetTrainTime(90, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8363  }
8364  else
8365  {
8366  PartStr = Utilities->Format96HHMM(GetTrainTime(91, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8367  }
8368  }
8369  else
8370  {
8371  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8372  {
8373  double MDTdouble = Ptr->MinDwellTime / 60;
8374  double MDT = int(MDTdouble * 10);
8375  MDT = MDT / 10;
8376  MinMinsString = "mins";
8377  if((MDT < 1.1) && (MDT > 0.9))
8378  {
8379  MinMinsString = "min";
8380  }
8381  PartStr = Utilities->Format96HHMM(GetTrainTime(94, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8382  Utilities->Format96HHMM(GetTrainTime(96, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8383  }
8384  else
8385  {
8386  PartStr = Utilities->Format96HHMM(GetTrainTime(98, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8387  Utilities->Format96HHMM(GetTrainTime(100, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8388  }
8389  Count++; // because there are 2 entries
8390  }
8391  }
8392  }
8393  else if((Ptr->FormatType == TimeTimeLoc) && !FirstPass)
8394  {
8395  AnsiString TrainLoc = "";
8396  if((TrainAtLocation(2, TrainLoc)) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8397  {
8398  PartStr = Utilities->Format96HHMM(GetTrainTime(41, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8399  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8400  {
8401  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8402  }
8403  }
8404  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8405  {
8406  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8407  {
8408  double MDTdouble = Ptr->MinDwellTime / 60;
8409  double MDT = int(MDTdouble * 10);
8410  MDT = MDT / 10;
8411  MinMinsString = "mins";
8412  if((MDT < 1.1) && (MDT > 0.9))
8413  {
8414  MinMinsString = "min";
8415  }
8416  PartStr = Utilities->Format96HHMM(GetTrainTime(92, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8417  }
8418  else
8419  {
8420  PartStr = Utilities->Format96HHMM(GetTrainTime(93, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8421  }
8422  }
8423  else
8424  {
8425  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8426  {
8427  double MDTdouble = Ptr->MinDwellTime / 60;
8428  double MDT = int(MDTdouble * 10);
8429  MDT = MDT / 10;
8430  MinMinsString = "mins";
8431  if((MDT < 1.1) && (MDT > 0.9))
8432  {
8433  MinMinsString = "min";
8434  }
8435  PartStr = Utilities->Format96HHMM(GetTrainTime(95, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8436  Utilities->Format96HHMM(GetTrainTime(97, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8437  }
8438  else
8439  {
8440  PartStr = Utilities->Format96HHMM(GetTrainTime(99, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8441  Utilities->Format96HHMM(GetTrainTime(101, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8442  }
8443  Count++; // because there are 2 entries
8444  }
8445  }
8446  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1))) //arrival
8447  {
8448  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8449  {
8450  double MDTdouble = Ptr->MinDwellTime / 60;
8451  double MDT = int(MDTdouble * 10);
8452  MDT = MDT / 10;
8453  MinMinsString = "mins";
8454  if((MDT < 1.1) && (MDT > 0.9))
8455  {
8456  MinMinsString = "min";
8457  }
8458  PartStr = Utilities->Format96HHMM(GetTrainTime(88, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8459  }
8460  else
8461  {
8462  PartStr = Utilities->Format96HHMM(GetTrainTime(83, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName;
8463  }
8464  }
8465  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1))) //departure
8466  {
8467  PartStr = Utilities->Format96HHMM(GetTrainTime(19, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8468  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8469  {
8470  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8471  }
8472  }
8473  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture && (TrainAtLocation(5, TrainLoc)) && (TrainLoc == Ptr->LocationName)) //added at v2.12.0 for becoming new service early (see BecomeNewService)
8474  { //note that TreatPassAsTimeLocDeparture can't be set if have SkippedDeparture
8475  //added last 2 condits at v2.20.1 as otherwise all later passes listed as departures (see Albie Vowles' email of 25/07/24)
8476  PartStr = Utilities->Format96HHMM(GetTrainTime(47, Ptr->EventTime)) + ": Depart from " + Ptr->LocationName;
8477  }
8478  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
8479  {
8480  PartStr = Utilities->Format96HHMM(GetTrainTime(30, Ptr->EventTime)) + ": Pass " + Ptr->LocationName;
8481  }
8482  else if(Ptr->Command == "Fns")
8483  {
8484  PartStr = Utilities->Format96HHMM(GetTrainTime(20, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(15,
8485  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8486  PartStr = GetNewServiceDepartureInfo(5, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to PartStr
8487  }
8488  else if(Ptr->Command == "F-nshs")
8489  {
8490  PartStr = Utilities->Format96HHMM(GetTrainTime(35, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " +
8491  Ptr->LocationName;
8492  PartStr = GetNewServiceDepartureInfo(6, Ptr, 0, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8493  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
8494  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
8495  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
8496  }
8497  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8498  {
8499  PartStr = Utilities->Format96HHMM(GetTrainTime(21, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(16,
8500  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8501  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8502  PartStr = GetNewServiceDepartureInfo(7, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8503  }
8504  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8505  {
8506  PartStr = Utilities->Format96HHMM(GetTrainTime(22, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
8507  +" at " + Ptr->LocationName;
8508  PartStr = GetNewServiceDepartureInfo(8, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8509  }
8510  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8511  {
8512  PartStr = Utilities->Format96HHMM(GetTrainTime(23, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(17,
8513  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8514  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8515  PartStr = GetNewServiceDepartureInfo(9, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8516  }
8517  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8518  {
8519  PartStr = "Terminate at " + Ptr->LocationName;
8520  }
8521  else if(Ptr->Command == "Frh")
8522  {
8523  PartStr = "Terminate at " + Ptr->LocationName;
8524  }
8525  else if(Ptr->Command == "Fer")
8526  {
8527  AnsiString AllowedExits = "";
8528  PartStr = Utilities->Format96HHMM(GetTrainTime(24, Ptr->EventTime)) + ": Exit railway" + TrainController->GetExitLocationAndAt(2, Ptr->ExitList, AllowedExits) + AllowedExits;
8529  }
8530  else if(Ptr->Command == "Fjo")
8531  {
8532  PartStr = Utilities->Format96HHMM(GetTrainTime(25, Ptr->EventTime)) + ": Join " + TrainController->GetRepeatHeadCode(18, Ptr->OtherHeadCode,
8533  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8534  }
8535  else if(Ptr->Command == "jbo")
8536  {
8537  PartStr = Utilities->Format96HHMM(GetTrainTime(26, Ptr->EventTime)) + ": Joined by " + TrainController->GetRepeatHeadCode(19, Ptr->OtherHeadCode,
8538  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8539  }
8540  else if(Ptr->Command == "fsp")
8541  {
8542  PartStr = Utilities->Format96HHMM(GetTrainTime(27, Ptr->EventTime)) + ": Front split to " + TrainController->GetRepeatHeadCode(20,
8543  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8544  if(Ptr->SplitDistribution != "")
8545  {
8546  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8547  }
8548  else
8549  {
8550  PartStr+= ", split mass%-Power% = 50-50";
8551  }
8552  }
8553  else if(Ptr->Command == "rsp")
8554  {
8555  PartStr = Utilities->Format96HHMM(GetTrainTime(28, Ptr->EventTime)) + ": Rear split to " + TrainController->GetRepeatHeadCode(21,
8556  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8557  if(Ptr->SplitDistribution != "")
8558  {
8559  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8560  }
8561  else
8562  {
8563  PartStr+= ", split mass%-Power% = 50-50";
8564  }
8565  }
8566  else if(Ptr->Command == "cdt")
8567  {
8568  PartStr = Utilities->Format96HHMM(GetTrainTime(29, Ptr->EventTime)) + ": Change direction at " + Ptr->LocationName;
8569  }
8570  else if(Ptr->Command == "dsc")
8571  {
8572  PartStr = Utilities->Format96HHMM(GetTrainTime(67, Ptr->EventTime)) + ": Change description at " + Ptr->LocationName;
8573  }
8574  else if(Ptr->Command == "cms")
8575  {
8576  PartStr = Utilities->Format96HHMM(GetTrainTime(77, Ptr->EventTime)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
8577  }
8578  if(RetStr != "")
8579  {
8580  RetStr = RetStr + '\n' + PartStr;
8581  }
8582  else
8583  {
8584  RetStr = PartStr;
8585  }
8586  FirstPass = false;
8587  Count++;
8588 
8589  if(SkipDep)
8590  {
8591  Ptr = &(TrainDataEntryPtr->ActionVector.at(0)) + SkipPtrValue;
8592  Ptr--; //it is incremented at the start of the next loop
8593  SkipDep = false;
8594  SkipDepActedOn = true;
8595  }
8596  }
8597  while(!TimetableFinished && (Count < 32) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
8598  // limit of 32 allows a max of 34 entries (33 + 1 for the new service departure time) (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and
8599  // train status gives a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
8600  // forward as anyone should wish to see without looking at the full timetable
8601  if(TimetableFinished)
8602  {
8603  if(TrainMode == Timetable)
8604  {
8605  RetStr = "Timetable finished";
8606  }
8607  else
8608  {
8609  RetStr = "No timetable";
8610  }
8611  }
8612  Utilities->CallLogPop(1125);
8613  return("Timetable:\n" + RetStr);
8614 }
8615 
8616 // ---------------------------------------------------------------------------
8617 
8618 void TTrain::SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
8619 {
8620  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveOneSessionTrain" + "," + HeadCode);
8621  Utilities->SaveFileString(OutFile, HeadCode);
8624  Utilities->SaveFileInt(OutFile, StartSpeed);
8627  Utilities->SaveFileInt(OutFile, RepeatNumber);
8630  Utilities->SaveFileInt(OutFile, Mass);
8633  Utilities->SaveFileDouble(OutFile, EntrySpeed);
8640  Utilities->SaveFileDouble(OutFile, BrakeRate);
8644  Utilities->SaveFileDouble(OutFile, double(EntryTime));
8645  Utilities->SaveFileDouble(OutFile, double(ExitTimeHalf));
8646  Utilities->SaveFileDouble(OutFile, double(ExitTimeFull));
8647  Utilities->SaveFileDouble(OutFile, double(ReleaseTime));
8648  Utilities->SaveFileDouble(OutFile, double(TRSTime));
8649  Utilities->SaveFileDouble(OutFile, double(LastActionTime));
8653  Utilities->SaveFileInt(OutFile, (short)TrainMode);
8658  Utilities->SaveFileBool(OutFile, Derailed);
8660  Utilities->SaveFileBool(OutFile, Crashed);
8667  Utilities->SaveFileBool(OutFile, NotInService);
8668  Utilities->SaveFileBool(OutFile, Plotted);
8669  Utilities->SaveFileBool(OutFile, TrainGone);
8670  Utilities->SaveFileBool(OutFile, SPADFlag);
8672  Utilities->SaveFileInt(OutFile, HOffset[0]);
8673  Utilities->SaveFileInt(OutFile, HOffset[1]);
8674  Utilities->SaveFileInt(OutFile, HOffset[2]);
8675  Utilities->SaveFileInt(OutFile, HOffset[3]);
8676  Utilities->SaveFileInt(OutFile, VOffset[0]);
8677  Utilities->SaveFileInt(OutFile, VOffset[1]);
8678  Utilities->SaveFileInt(OutFile, VOffset[2]);
8679  Utilities->SaveFileInt(OutFile, VOffset[3]);
8680  Utilities->SaveFileInt(OutFile, PlotElement[0]);
8681  Utilities->SaveFileInt(OutFile, PlotElement[1]);
8682  Utilities->SaveFileInt(OutFile, PlotElement[2]);
8683  Utilities->SaveFileInt(OutFile, PlotElement[3]);
8684  Utilities->SaveFileInt(OutFile, PlotEntryPos[0]);
8685  Utilities->SaveFileInt(OutFile, PlotEntryPos[1]);
8686  Utilities->SaveFileInt(OutFile, PlotEntryPos[2]);
8687  Utilities->SaveFileInt(OutFile, PlotEntryPos[3]);
8689  Utilities->SaveFileInt(OutFile, (short)Straddle);
8690  Utilities->SaveFileInt(OutFile, NextTrainID);
8691  Utilities->SaveFileInt(OutFile, TrainID);
8692  Utilities->SaveFileInt(OutFile, LeadElement);
8693  Utilities->SaveFileInt(OutFile, LeadEntryPos);
8694  Utilities->SaveFileInt(OutFile, LeadExitPos);
8695  Utilities->SaveFileInt(OutFile, MidElement);
8696  Utilities->SaveFileInt(OutFile, MidEntryPos);
8697  Utilities->SaveFileInt(OutFile, MidExitPos);
8698  Utilities->SaveFileInt(OutFile, LagElement);
8699  Utilities->SaveFileInt(OutFile, LagEntryPos);
8700  Utilities->SaveFileInt(OutFile, LagExitPos);
8701  int ColourNumber;
8702 
8704  {
8705  ColourNumber = 0;
8706  }
8708  {
8709  ColourNumber = 1;
8710  }
8712  {
8713  ColourNumber = 2;
8714  }
8716  {
8717  ColourNumber = 3;
8718  }
8720  {
8721  ColourNumber = 4;
8722  }
8724  {
8725  ColourNumber = 5;
8726  }
8728  {
8729  ColourNumber = 6;
8730  }
8732  {
8733  ColourNumber = 7;
8734  }
8736  {
8737  ColourNumber = 8;
8738  }
8740  {
8741  ColourNumber = 9;
8742  }
8744  {
8745  ColourNumber = 10;
8746  }
8748  {
8749  ColourNumber = 11;
8750  }
8752  {
8753  ColourNumber = 12;
8754  }
8755  else if(BackgroundColour == clTRSBackground)
8756  {
8757  ColourNumber = 13;
8758  }
8760  {
8761  ColourNumber = 14; // added at v2.4.0
8762  }
8763  Utilities->SaveFileInt(OutFile, ColourNumber);
8764 
8765  // additional data
8766  bool ForwardHeadCode;
8767 
8768  if(HeadCodePosition[3] == HeadCodeGrPtr[3])
8769  {
8770  ForwardHeadCode = true;
8771  }
8772  // can't use 'if(HeadCodePosition[0] == HeadCodeGrPtr[0])' as HeadCodePosition[0] is set to FrontCodePtr
8773  else
8774  {
8775  ForwardHeadCode = false;
8776  }
8777  Utilities->SaveFileBool(OutFile, ForwardHeadCode);
8778 
8779  int TrainDataEntryValue = TrainDataEntryPtr - &(TrainController->TrainDataVector.at(0));
8780 
8781  Utilities->SaveFileInt(OutFile, TrainDataEntryValue);
8782  int ActionVectorEntryValue = ActionVectorEntryPtr - &(TrainDataEntryPtr->ActionVector.at(0));
8783 
8784  Utilities->SaveFileInt(OutFile, ActionVectorEntryValue);
8785  // now the marker comes next which was ****** originally but used for RestoreTimetableLocation as well some time ago (came before the asterisks)
8786  // but at v2.4.0 need to include StoppedWithoutPower, while keeping length of marker at 6, because that is tested in earlier versions
8787  // so use the last asterisk position for this - 0 for false & 1 for true
8788  // note that failed train data is handled in InterfaceUnit.cpp & stored after the performance file
8789  AnsiString Marker;
8790 
8792  {
8793  Marker = "*****1";
8794  }
8795  else
8796  {
8797  Marker = "*****0";
8798  }
8799  if(RestoreTimetableLocation == "")
8800  {
8801  Utilities->SaveFileString(OutFile, Marker);
8802  }
8803  else
8804  {
8805  AnsiString CombinedString = RestoreTimetableLocation + Marker;
8806  Utilities->SaveFileString(OutFile, CombinedString);
8807  // RestoreTimetableLocation + marker
8808  }
8809  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - it should have been saved earlier
8810  Utilities->CallLogPop(1457);
8811 }
8812 
8813 // ---------------------------------------------------------------------------
8814 
8815 void TTrain::LoadOneSessionTrain(int Caller, std::ifstream &InFile)
8816 {
8817  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadOneSessionTrain"); // don't have headcode yet
8818  HeadCode = Utilities->LoadFileString(InFile);
8821  StartSpeed = Utilities->LoadFileInt(InFile);
8823  if(SignallerMaxSpeed < 10)
8824  {
8825  SignallerMaxSpeed = 10; // added at v0.6 to avoid low max speeds
8826  }
8828  RepeatNumber = Utilities->LoadFileInt(InFile);
8831  Mass = Utilities->LoadFileInt(InFile);
8834  {
8836  }
8837  // above added at v2.1.0 for legacy session files where value may not have been limited
8839  EntrySpeed = Utilities->LoadFileDouble(InFile);
8843  if(TimetableMaxRunningSpeed < 10)
8844  {
8845  TimetableMaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8846  }
8848  if(MaxRunningSpeed < 10)
8849  {
8850  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8851  }
8854  BrakeRate = Utilities->LoadFileDouble(InFile);
8858  EntryTime = TDateTime(Utilities->LoadFileDouble(InFile));
8859  ExitTimeHalf = TDateTime(Utilities->LoadFileDouble(InFile));
8860  ExitTimeFull = TDateTime(Utilities->LoadFileDouble(InFile));
8861  ReleaseTime = TDateTime(Utilities->LoadFileDouble(InFile));
8862  TRSTime = TDateTime(Utilities->LoadFileDouble(InFile));
8863  LastActionTime = TDateTime(Utilities->LoadFileDouble(InFile));
8872  Derailed = Utilities->LoadFileBool(InFile);
8874  Crashed = Utilities->LoadFileBool(InFile);
8881  NotInService = Utilities->LoadFileBool(InFile);
8882  Plotted = Utilities->LoadFileBool(InFile);
8883  TrainGone = Utilities->LoadFileBool(InFile);
8884  SPADFlag = Utilities->LoadFileBool(InFile);
8886  HOffset[0] = Utilities->LoadFileInt(InFile);
8887  HOffset[1] = Utilities->LoadFileInt(InFile);
8888  HOffset[2] = Utilities->LoadFileInt(InFile);
8889  HOffset[3] = Utilities->LoadFileInt(InFile);
8890  VOffset[0] = Utilities->LoadFileInt(InFile);
8891  VOffset[1] = Utilities->LoadFileInt(InFile);
8892  VOffset[2] = Utilities->LoadFileInt(InFile);
8893  VOffset[3] = Utilities->LoadFileInt(InFile);
8894  PlotElement[0] = Utilities->LoadFileInt(InFile);
8895  PlotElement[1] = Utilities->LoadFileInt(InFile);
8896  PlotElement[2] = Utilities->LoadFileInt(InFile);
8897  PlotElement[3] = Utilities->LoadFileInt(InFile);
8898  PlotEntryPos[0] = Utilities->LoadFileInt(InFile);
8899  PlotEntryPos[1] = Utilities->LoadFileInt(InFile);
8900  PlotEntryPos[2] = Utilities->LoadFileInt(InFile);
8901  PlotEntryPos[3] = Utilities->LoadFileInt(InFile);
8903  Straddle = (TStraddle)(Utilities->LoadFileInt(InFile));
8904  NextTrainID = Utilities->LoadFileInt(InFile);
8905  // will be same for all but best to save all anyway
8906  TrainID = Utilities->LoadFileInt(InFile);
8907  LeadElement = Utilities->LoadFileInt(InFile);
8908  LeadEntryPos = Utilities->LoadFileInt(InFile);
8909  LeadExitPos = Utilities->LoadFileInt(InFile);
8910  MidElement = Utilities->LoadFileInt(InFile);
8911  MidEntryPos = Utilities->LoadFileInt(InFile);
8912  MidExitPos = Utilities->LoadFileInt(InFile);
8913  LagElement = Utilities->LoadFileInt(InFile);
8914  LagEntryPos = Utilities->LoadFileInt(InFile);
8915  LagExitPos = Utilities->LoadFileInt(InFile);
8916  int ColourNumber = TColor(Utilities->LoadFileInt(InFile));
8917 
8918  if(ColourNumber == 0)
8919  {
8921  }
8922  else if(ColourNumber == 1)
8923  {
8925  }
8926  else if(ColourNumber == 2)
8927  {
8929  }
8930  else if(ColourNumber == 3)
8931  {
8933  }
8934  else if(ColourNumber == 4)
8935  {
8937  }
8938  else if(ColourNumber == 5)
8939  {
8941  }
8942  else if(ColourNumber == 6)
8943  {
8945  }
8946  else if(ColourNumber == 7)
8947  {
8949  }
8950  else if(ColourNumber == 8)
8951  {
8953  }
8954  else if(ColourNumber == 9)
8955  {
8957  }
8958  else if(ColourNumber == 10)
8959  {
8961  }
8962  else if(ColourNumber == 11)
8963  {
8965  }
8966  else if(ColourNumber == 12)
8967  {
8969  }
8970  else if(ColourNumber == 13)
8971  {
8973  }
8974  else if(ColourNumber == 14)
8975  {
8976  BackgroundColour = clTrainFailedBackground; // added at v2.4.0
8977 
8978  }
8979  // additional data
8981  // sets the BackgroundColour to the loaded value
8982  bool ForwardHeadCode = Utilities->LoadFileBool(InFile);
8983 
8984  if(ForwardHeadCode)
8985  {
8986  for(int x = 0; x < 4; x++)
8987  {
8989  }
8990  }
8991  else
8992  {
8993  for(int x = 0; x < 4; x++)
8994  {
8995  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
8996  }
8997  }
8998  // if crashed & in timetable mode then change FrontCodePtr to black, if in signaller mode then change to blue whether crashed or not
8999  if(TrainMode == Timetable)
9000  {
9001  if(Crashed)
9002  {
9004  }
9005  else
9006  {
9008  }
9009  }
9010  else
9011  {
9013  }
9015  // pick up background bitmaps, none if MidLag as no train plotted - entering at continuation
9016  if(Straddle == LeadMid)
9017  {
9018  if(LeadElement > -1)
9019  {
9021  }
9022  if(LeadElement > -1)
9023  {
9025  }
9026  if(MidElement > -1)
9027  {
9029  }
9030  if(MidElement > -1)
9031  {
9033  }
9034  }
9035  else if(Straddle == LeadMidLag)
9036  {
9037  if(LeadElement > -1)
9038  {
9040  }
9041  if(MidElement > -1)
9042  {
9044  }
9045  if(MidElement > -1)
9046  {
9048  }
9049  if(LagElement > -1)
9050  {
9052  }
9053  }
9054  int TrainDataEntryValue = Utilities->LoadFileInt(InFile);
9055 
9056  TrainDataEntryPtr = &(TrainController->TrainDataVector.at(0)) + TrainDataEntryValue;
9057  int ActionVectorEntryValue = Utilities->LoadFileInt(InFile);
9058 
9059  ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)) + ActionVectorEntryValue;
9060 
9061  // need to set the TrainID if arriving at a continuation but hasn't been plotted yet
9062  if(LeadElement > -1)
9063  // need to include this in case train exiting & no lead element
9064  {
9066  {
9067  Track->TrackElementAt(668, LeadElement).TrainIDOnElement = TrainID; // no need to stop gap flashing if a continuation
9068  }
9069  }
9070  AValue = sqrt(2 * PowerAtRail / Mass);
9071 
9072  AnsiString LocationAndMarker = Utilities->LoadFileString(InFile);
9073 
9074  // possible RestoreTimetableLocation + Marker, where Marker is
9075  // "*****0" for !StoppedWithoutPower and "*****1" otherwise (from v2.4.0)
9076  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - RestoreTimetableLocation should have been saved earlier
9077  // added at beta v0.2e
9078  if((LocationAndMarker[1] != '*') && (LocationAndMarker.Length() > 6))
9079  // name not allowed to include the '*' character
9080  {
9081  AnsiString Location = LocationAndMarker.SubString(1, LocationAndMarker.Length() - 6);
9082  bool GiveMessagesFalse = false;
9083  bool CheckLocationsExistInRailwayTrue = true;
9084  if(TrainController->CheckLocationValidity(3, Location, GiveMessagesFalse, CheckLocationsExistInRailwayTrue))
9085  {
9086  // otherwise take no action
9087  RestoreTimetableLocation = Location;
9088  }
9089  }
9090  AnsiString Marker = LocationAndMarker.SubString(LocationAndMarker.Length() - 5, 6);
9091 
9092  StoppedWithoutPower = false;
9093  if(Marker[6] == '1')
9094  {
9095  StoppedWithoutPower = true;
9096  }
9097  Utilities->CallLogPop(1458);
9098 }
9099 
9100 // ---------------------------------------------------------------------------
9101 
9102 bool TTrain::CheckOneSessionTrain(std::ifstream &InFile)
9103 {
9105  {
9106  return(false); // HeadCode
9107 
9108  }
9109  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9110  {
9111  return(false); // RearStartElement
9112 
9113  }
9114  if(!Utilities->CheckFileInt(InFile, 0, 3))
9115  {
9116  return(false); // RearStartExitPos
9117 
9118  }
9119  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
9120  {
9121  return(false); // StartSpeed
9122 
9123  }
9124  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
9125  {
9126  return(false); // SignallerMaxSpeed
9127 
9128  }
9129  if(!Utilities->CheckFileBool(InFile))
9130  {
9131  return(false); // HoldAtLocationInTTMode
9132 
9133  }
9134  if(!Utilities->CheckFileInt(InFile, 0, 5760))
9135  {
9136  return(false); // RepeatNumber (max 96 x 60 at 1 min intervals)
9137 
9138  }
9139  if(!Utilities->CheckFileInt(InFile, 0, 5760))
9140  {
9141  return(false); // IncrementalMinutes (max 96 x 60)
9142 
9143  }
9144  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9145  {
9146  return(false); // IncrementalDigits
9147 
9148  }
9149  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
9150  {
9151  return(false); // Mass
9152 
9153  }
9154  if(!Utilities->CheckFileInt(InFile, 0, 100000000))
9155  {
9156  return(false);
9157  }
9158  // FrontElementSpeedLimit - changed at v2.1.0 - effectively
9159  // not checked so as to allow for legacy session files, for new session files limit is set when loaded, see above
9160  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
9161  {
9162  return(false); // FrontElementLength
9163 
9164  }
9165  if(!Utilities->CheckFileDouble(InFile))
9166  {
9167  return(false); // EntrySpeed
9168 
9169  }
9170  if(!Utilities->CheckFileDouble(InFile))
9171  {
9172  return(false); // ExitSpeedHalf
9173 
9174  }
9175  if(!Utilities->CheckFileDouble(InFile))
9176  {
9177  return(false); // ExitSpeedFull
9178 
9179  }
9180  if(!Utilities->CheckFileDouble(InFile))
9181  {
9182  return(false); // TimetableMaxRunningSpeed
9183 
9184  }
9185  if(!Utilities->CheckFileDouble(InFile))
9186  {
9187  return(false); // MaxRunningSpeed
9188 
9189  }
9190  if(!Utilities->CheckFileDouble(InFile))
9191  {
9192  return(false); // MaxExitSpeed
9193 
9194  }
9195  if(!Utilities->CheckFileDouble(InFile))
9196  {
9197  return(false); // MaxBrakeRate
9198 
9199  }
9200  if(!Utilities->CheckFileDouble(InFile))
9201  {
9202  return(false); // BrakeRate
9203 
9204  }
9205  if(!Utilities->CheckFileDouble(InFile))
9206  {
9207  return(false); // PowerAtRail
9208 
9209  }
9210  if(!Utilities->CheckFileBool(InFile))
9211  {
9212  return(false); // FirstHalfMove
9213 
9214  }
9215  if(!Utilities->CheckFileBool(InFile))
9216  {
9217  return(false); // OneLengthAccelDecel
9218 
9219  }
9220  if(!Utilities->CheckFileDouble(InFile))
9221  {
9222  return(false); // double(EntryTime)
9223 
9224  }
9225  if(!Utilities->CheckFileDouble(InFile))
9226  {
9227  return(false); // double(ExitTimeHalf)
9228 
9229  }
9230  if(!Utilities->CheckFileDouble(InFile))
9231  {
9232  return(false); // double(ExitTimeFull)
9233 
9234  }
9235  if(!Utilities->CheckFileDouble(InFile))
9236  {
9237  return(false); // double(ReleaseTime)
9238 
9239  }
9240  if(!Utilities->CheckFileDouble(InFile))
9241  {
9242  return(false); // double(TRSTime)
9243 
9244  }
9245  if(!Utilities->CheckFileDouble(InFile))
9246  {
9247  return(false); // double(LastActionTime)
9248 
9249  }
9250  if(!Utilities->CheckFileBool(InFile))
9251  {
9252  return(false); // CallingOnFlag
9253 
9254  }
9255  if(!Utilities->CheckFileBool(InFile))
9256  {
9257  return(false); // BeingCalledOn
9258 
9259  }
9260  if(!Utilities->CheckFileBool(InFile))
9261  {
9262  return(false); // DepartureTimeSet
9263 
9264  }
9265  if(!Utilities->CheckFileInt(InFile, 0, 2))
9266  {
9267  return(false); // (short)TrainMode
9268 
9269  }
9270  if(!Utilities->CheckFileBool(InFile))
9271  {
9272  return(false); // TimetableFinished
9273 
9274  }
9275  if(!Utilities->CheckFileBool(InFile))
9276  {
9277  return(false); // LastActionDelayFlag
9278 
9279  }
9280  if(!Utilities->CheckFileBool(InFile))
9281  {
9282  return(false); // SignallerRemoved
9283 
9284  }
9285  if(!Utilities->CheckFileBool(InFile))
9286  {
9287  return(false); // TerminatedMessageSent
9288 
9289  }
9290  if(!Utilities->CheckFileBool(InFile))
9291  {
9292  return(false); // Derailed
9293 
9294  }
9295  if(!Utilities->CheckFileBool(InFile))
9296  {
9297  return(false); // DerailPending
9298 
9299  }
9300  if(!Utilities->CheckFileBool(InFile))
9301  {
9302  return(false); // Crashed
9303 
9304  }
9305  if(!Utilities->CheckFileBool(InFile))
9306  {
9307  return(false); // StoppedAtBuffers
9308 
9309  }
9310  if(!Utilities->CheckFileBool(InFile))
9311  {
9312  return(false); // StoppedAtSignal
9313 
9314  }
9315  if(!Utilities->CheckFileBool(InFile))
9316  {
9317  return(false); // StoppedAtLocation
9318 
9319  }
9320  if(!Utilities->CheckFileBool(InFile))
9321  {
9322  return(false); // SignallerStopped
9323 
9324  }
9325  if(!Utilities->CheckFileBool(InFile))
9326  {
9327  return(false); // StoppedAfterSPAD
9328 
9329  }
9330  if(!Utilities->CheckFileBool(InFile))
9331  {
9332  return(false); // StoppedForTrainInFront
9333 
9334  }
9335  if(!Utilities->CheckFileBool(InFile))
9336  {
9337  return(false); // NotInService
9338 
9339  }
9340  if(!Utilities->CheckFileBool(InFile))
9341  {
9342  return(false); // Plotted
9343 
9344  }
9345  if(!Utilities->CheckFileBool(InFile))
9346  {
9347  return(false); // TrainGone
9348 
9349  }
9350  if(!Utilities->CheckFileBool(InFile))
9351  {
9352  return(false); // SPADFlag
9353 
9354  }
9355  if(!Utilities->CheckFileBool(InFile))
9356  {
9357  return(false); // TimeTimeLocArrived
9358 
9359  }
9360  if(!Utilities->CheckFileInt(InFile, 0, 15))
9361  {
9362  return(false); // HOffset[0]
9363 
9364  }
9365  if(!Utilities->CheckFileInt(InFile, 0, 15))
9366  {
9367  return(false); // HOffset[1]
9368 
9369  }
9370  if(!Utilities->CheckFileInt(InFile, 0, 15))
9371  {
9372  return(false); // HOffset[2]
9373 
9374  }
9375  if(!Utilities->CheckFileInt(InFile, 0, 15))
9376  {
9377  return(false); // HOffset[3]
9378 
9379  }
9380  if(!Utilities->CheckFileInt(InFile, 0, 15))
9381  {
9382  return(false); // VOffset[0]
9383 
9384  }
9385  if(!Utilities->CheckFileInt(InFile, 0, 15))
9386  {
9387  return(false); // VOffset[1]
9388 
9389  }
9390  if(!Utilities->CheckFileInt(InFile, 0, 15))
9391  {
9392  return(false); // VOffset[2]
9393 
9394  }
9395  if(!Utilities->CheckFileInt(InFile, 0, 15))
9396  {
9397  return(false); // VOffset[3]
9398 
9399  }
9400  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9401  {
9402  return(false); // PlotElement[0]
9403 
9404  }
9405  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9406  {
9407  return(false); // PlotElement[1]
9408 
9409  }
9410  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9411  {
9412  return(false); // PlotElement[2]
9413 
9414  }
9415  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9416  {
9417  return(false); // PlotElement[3]
9418 
9419  }
9420  if(!Utilities->CheckFileInt(InFile, 0, 3))
9421  {
9422  return(false); // PlotEntryPos[0]
9423 
9424  }
9425  if(!Utilities->CheckFileInt(InFile, 0, 3))
9426  {
9427  return(false); // PlotEntryPos[1]
9428 
9429  }
9430  if(!Utilities->CheckFileInt(InFile, 0, 3))
9431  {
9432  return(false); // PlotEntryPos[2]
9433 
9434  }
9435  if(!Utilities->CheckFileInt(InFile, 0, 3))
9436  {
9437  return(false); // PlotEntryPos[3]
9438 
9439  }
9440  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9441  {
9442  return(false); // TrainCrashedInto
9443 
9444  }
9445  if(!Utilities->CheckFileInt(InFile, 0, 2))
9446  {
9447  return(false); // (short)Straddle
9448 
9449  }
9450  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9451  {
9452  return(false); // NextTrainID
9453 
9454  }
9455  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9456  {
9457  return(false); // TrainID
9458 
9459  }
9460  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9461  {
9462  return(false); // LeadElement
9463 
9464  }
9465  if(!Utilities->CheckFileInt(InFile, 0, 3))
9466  {
9467  return(false); // LeadEntryPos
9468 
9469  }
9470  if(!Utilities->CheckFileInt(InFile, 0, 3))
9471  {
9472  return(false); // LeadExitPos
9473 
9474  }
9475  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9476  {
9477  return(false); // MidElement
9478 
9479  }
9480  if(!Utilities->CheckFileInt(InFile, 0, 3))
9481  {
9482  return(false); // MidEntryPos
9483 
9484  }
9485  if(!Utilities->CheckFileInt(InFile, 0, 3))
9486  {
9487  return(false); // MidExitPos
9488 
9489  }
9490  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9491  {
9492  return(false); // LagElement
9493 
9494  }
9495  if(!Utilities->CheckFileInt(InFile, 0, 3))
9496  {
9497  return(false); // LagEntryPos
9498 
9499  }
9500  if(!Utilities->CheckFileInt(InFile, 0, 3))
9501  {
9502  return(false); // LagExitPos
9503 
9504  }
9505  if(!Utilities->CheckFileInt(InFile, 0, 14))
9506  {
9507  return(false);
9508  }
9509  // Background colour number //14 is failed colour at v2.4.0
9510  if(!Utilities->CheckFileBool(InFile))
9511  {
9512  return(false); // ForwardHeadCode
9513 
9514  }
9515  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9516  {
9517  return(false); // TrainDataEntryValue
9518 
9519  }
9520  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9521  {
9522  return(false); // ActionVectorEntryValue
9523 
9524  }
9526  {
9527  return(false); // End of train marker + possible RestoreTimetableLocation
9528 
9529  }
9530  // and StoppedWithoutPower flag
9531  return(true);
9532 }
9533 
9534 // ---------------------------------------------------------------------------
9535 
9536 void TTrain::PlotTrainInZoomOutMode(int Caller, bool Flash)
9537 {
9538  // order below reflects significance so earlier shows first, as may have more than one flag set
9539  // only plot flashing trains when Flash is true
9540 
9541 /*
9542  clCrashedBackground (TColor)0x0000FF red
9543  clDerailedBackground (TColor)0x0000FF red
9544  clSPADBackground (TColor)0x00FFFF yellow
9545  clTrainFailedBackground (TColor)0x0066FF orange new at v2.4.0
9546  clCallOnBackground (TColor)0xFF33FF light magenta
9547  clSignalStopBackground (TColor)0x00FF66 green
9548  clBufferAttentionNeeded (TColor)0xFFFF00 cyan
9549  clStationStopBackground (TColor)0xCCFFCC pale green
9550  clTRSBackground (TColor)0xFFCCFF light pink
9551  clBufferStopBackground (TColor)0xFFFFCC pale cyan
9552  clStoppedTrainInFront (TColor)0xFF9999 lavender blue
9553  clSignallerStopped (TColor)0x99CCFF caramel
9554  clNormalBackground (TColor)0xCCCCCC grey
9555 */
9556 
9557  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainInZoomOutMode" + "," + HeadCode);
9558  bool HideFlashingTrain = true;
9559  // hide it when Flash false so it blinks on and off
9560  // if don't hide it it stays displayed all the time
9561  Graphics::TBitmap *SmallTrainBitmap;
9562 
9563  // NB ensure retain same order as zoomed in order so colours correspond
9565  {
9566  TrainController->CrashWarning = true;
9567  SmallTrainBitmap = RailGraphics->smRed;
9568  }
9570  {
9572  SmallTrainBitmap = RailGraphics->smRed;
9573  }
9575  {
9576  TrainController->SPADWarning = true;
9577  SmallTrainBitmap = RailGraphics->smYellow;
9578  }
9580  {
9582  SmallTrainBitmap = RailGraphics->smOrange;
9583  }
9585  {
9587  SmallTrainBitmap = RailGraphics->smMagenta;
9588  }
9590  {
9592  SmallTrainBitmap = RailGraphics->smBrightGreen;
9593  }
9595  {
9597  SmallTrainBitmap = RailGraphics->smCyan;
9598  }
9600  {
9601  SmallTrainBitmap = RailGraphics->smPaleGreen;
9602  HideFlashingTrain = false;
9603  }
9605  {
9606  SmallTrainBitmap = RailGraphics->smCyan;
9607  HideFlashingTrain = false;
9608  }
9610  {
9611  SmallTrainBitmap = RailGraphics->smLightBlue;
9612  HideFlashingTrain = false;
9613  }
9615  {
9616  SmallTrainBitmap = RailGraphics->smCaramel;
9617  HideFlashingTrain = false;
9618  }
9619  else
9620  {
9621  SmallTrainBitmap = RailGraphics->smBlack; // moving
9622  HideFlashingTrain = false;
9623  }
9624  // now plot the new train
9625  // just plot lead & mid, unless lead == -1 in which case plot mid & lag
9626  if((LeadElement > -1) && (!HideFlashingTrain || Flash))
9627  {
9628  Display->PlotSmallOutput(4, Track->TrackElementAt(441, LeadElement).HLoc * 4, Track->TrackElementAt(442, LeadElement).VLoc * 4, SmallTrainBitmap);
9629  }
9630  if((MidElement > -1) && (!HideFlashingTrain || Flash))
9631  {
9632  Display->PlotSmallOutput(5, Track->TrackElementAt(443, MidElement).HLoc * 4, Track->TrackElementAt(444, MidElement).VLoc * 4, SmallTrainBitmap);
9633  }
9634  if((LeadElement == -1) && (LagElement > -1) && (!HideFlashingTrain || Flash))
9635  {
9636  Display->PlotSmallOutput(6, Track->TrackElementAt(445, LagElement).HLoc * 4, Track->TrackElementAt(446, LagElement).VLoc * 4, SmallTrainBitmap);
9637  }
9641  Utilities->CallLogPop(1459);
9642 }
9643 
9644 // ---------------------------------------------------------------------------
9645 
9647 {
9648  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrainInZoomOutMode," + AnsiString(TrainID) + "," + HeadCode);
9649  if(!Display->ZoomOutFlag)
9650  {
9651  Utilities->CallLogPop(1304);
9652  return;
9653  }
9654  for(int y = 0; y < 3; y++)
9655  {
9656  if(OldZoomOutElement[y] > -1)
9657  {
9658  bool FoundFlag = false;
9659  TTrackElement ATElement = Track->TrackElementAt(717, OldZoomOutElement[y]);
9660  TTrackElement IATElement1, IATElement2;
9661  // default elements to begin with
9662  Display->PlotSmallOutput(7, ATElement.HLoc * 4, ATElement.VLoc * 4, RailGraphics->smSolidBgnd); // plot the blank
9663  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(14, ATElement.HLoc, ATElement.VLoc, FoundFlag);
9664  // Note, have to plot inactives before track because track has to overwrite NamedLocationElements
9665  if(FoundFlag)
9666  {
9667  IATElement1 = Track->InactiveTrackElementAt(87, IMPair.first);
9668  Display->PlotSmallOutput(8, IATElement1.HLoc * 4, IATElement1.VLoc * 4, IATElement1.SmallGraphicPtr);
9669  if(IMPair.first != IMPair.second)
9670  {
9671  IATElement2 = Track->InactiveTrackElementAt(88, IMPair.second);
9672  Display->PlotSmallOutput(9, IATElement2.HLoc * 4, IATElement2.VLoc * 4, IATElement2.SmallGraphicPtr);
9673  }
9674  }
9675  Display->PlotSmallOutput(10, ATElement.HLoc * 4, ATElement.VLoc * 4, ATElement.SmallGraphicPtr);
9676  }
9677  }
9678  Utilities->CallLogPop(1305);
9679 }
9680 
9681 // ---------------------------------------------------------------------------
9682 
9683 bool TTrain::TrainAtLocation(int Caller, AnsiString &LocationName)
9684 {
9685  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainAtLocation" + "," + HeadCode);
9686  LocationName = "";
9687  if(!RevisedStoppedAtLoc())
9688  {
9689  Utilities->CallLogPop(1398);
9690  return(false);
9691  }
9692  if(LeadElement > -1)
9693  {
9695  }
9696  if((LocationName == "") && (MidElement > -1))
9697  {
9698  LocationName = Track->TrackElementAt(682, MidElement).ActiveTrackElementName;
9699  }
9700  if((LocationName == "") && (LagElement > -1))
9701  {
9702  LocationName = Track->TrackElementAt(683, LagElement).ActiveTrackElementName;
9703  }
9704  if(LocationName == "")
9705  {
9706  throw Exception("Error - Location name not set in TrainAtLocation");
9707  }
9708  Utilities->CallLogPop(1399);
9709  return(true);
9710 }
9711 
9712 // ---------------------------------------------------------------------------
9713 
9714 void TTrain::PlotTrain(int Caller, TDisplay *Disp)
9715 {
9716  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrain" + "," + HeadCode);
9717  for(int x = 0; x < 4; x++)
9718  {
9719  PlotTrainGraphic(7, x, Disp);
9720  }
9722  Utilities->CallLogPop(647);
9723 }
9724 
9725 // ---------------------------------------------------------------------------
9726 
9727 void TTrain::WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
9728 {
9729  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainToImage" + "," + HeadCode);
9730  for(int x = 0; x < 4; x++)
9731  {
9732  if(PlotElement[x] > -1)
9733  {
9734  Bitmap->Canvas->Draw(((Track->TrackElementAt(744, PlotElement[x]).HLoc - Track->GetHLocMin()) * 16 + HOffset[x]),
9735  ((Track->TrackElementAt(745, PlotElement[x]).VLoc - Track->GetVLocMin()) * 16 + VOffset[x]), HeadCodePosition[x]);
9736  }
9737  }
9739  {
9740  Bitmap->Canvas->Draw(LongServRefTextH - (Track->GetHLocMin() * 16), LongServRefTextV - (Track->GetVLocMin() * 16), ImageLongServRefBitmap);
9741  }
9742  Utilities->CallLogPop(1708);
9743 }
9744 
9745 // ---------------------------------------------------------------------------
9746 
9747 bool TTrain::LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber) // added at v1.2.0
9748 {
9749  // return true for any part of train occupying LinkNumber at TrackVectorPosition, false for anything else, including no LinkNumber & no TrackVectorPosition
9750  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LinkOccupied," + AnsiString(TrackVectorPosition) + "," +
9751  AnsiString(LinkNumber) + "," + HeadCode);
9752 
9753 /* Note on Straddle: Straddle defines the actual train position wrt Lag, Mid & Lead elements at all times other than within UpdateTrain. Is only MidLag outside UpdateTrain
9754  on first entry at a continuation (with no train plotted), and that has no relevance here. In all other cases it is either LeadMid (when train fully
9755  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
9756 */
9757 
9758  // note that MidElement always fully occupied
9759  if((MidElement == TrackVectorPosition) && ((Track->TrackElementAt(883, TrackVectorPosition).Link[MidEntryPos] == LinkNumber) || (Track->TrackElementAt(884,
9760  TrackVectorPosition).Link[MidExitPos] == LinkNumber)))
9761  {
9762  Utilities->CallLogPop(2005);
9763  return(true);
9764  }
9765  if(Straddle == LeadMid)
9766  {
9767  if((LeadElement == TrackVectorPosition) && ((Track->TrackElementAt(885, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber) ||
9768  (Track->TrackElementAt(886, TrackVectorPosition).Link[LeadExitPos] == LinkNumber)))
9769  {
9770  Utilities->CallLogPop(2006);
9771  return(true);
9772  }
9773  }
9774  else if(Straddle == LeadMidLag)
9775  {
9776  if((LeadElement == TrackVectorPosition) && (Track->TrackElementAt(887, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber))
9777  // only interested in LeadEntryPos as train not occupying ExitPos yet
9778  {
9779  Utilities->CallLogPop(2007);
9780  return(true);
9781  }
9782  else if((LagElement == TrackVectorPosition) && (Track->TrackElementAt(888, TrackVectorPosition).Link[LagExitPos] == LinkNumber))
9783  // only interested in LagExitPos as train has left EntryPos
9784  {
9785  Utilities->CallLogPop(2008);
9786  return(true);
9787  }
9788  }
9789  Utilities->CallLogPop(2009);
9790  return(false);
9791 }
9792 
9793 // ---------------------------------------------------------------------------
9794 
9795 float TTrain::CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair) // only called for running trains.
9800 // TimeToExit & ExitPair added for multiplayer
9801 {
9802  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcTimeToAct, " + HeadCode);
9803  int DistanceToRedSignal = 0, DistanceToExit = -1;
9804  float TimeToAct = 0, LastTimeToExit = TimeToExit;
9805  TimeToExit = -1;
9806  ExitPair.first = -1;
9807  ExitPair.second = -1;
9808  float MinsEarly = 0; //added at v2.6.1
9809  TDateTime DepartureTime; //added at v2.6.1 //ArrivalTime used instead of this at v2.9.0 but still calculate it in case need it later for some reason
9810  TDateTime ArrivalTime; //added at v2.9.0 as MinsEarly used DepartureTime which wasn't correct
9811  float TempTTE;
9812 
9813  if(TrainFailed && Stopped() && (TrainMode != Signaller))
9814  {
9815  Utilities->CallLogPop(2147);
9816  return(0); // time to act now, time to exit set above
9817  }
9818  if(SignallerStopped)
9819  {
9820  Utilities->CallLogPop(2080);
9821  return(-1); //time to exit set above
9822  }
9823  if(BeingCalledOn) //added at v2.7.0 so zero time to act cancelled right away
9824  {
9825  Utilities->CallLogPop(2266);
9826  return(-1); // time to exit set above
9827  }
9828 
9829  // check if exiting at a continuation, if so there's no action time needed but still need exit time & ExitPair
9830  if((LeadElement == -1) && (MidElement == -1) && (LagElement > -1) && (Track->TrackElementAt(1411, LagElement).TrackType == Continuation)
9831  /*&& (ExitSpeedFull > 1)*/) //LagElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9832  {
9833  if(Straddle == LeadMidLag) //only half of rear train element on exit, 0.5 lengths to exit
9834  {
9835  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9836  {
9837  TempTTE = (0.5 * Track->TrackElementAt(1412, LagElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9838  if(TempTTE < LastTimeToExit)
9839  {
9840  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9841  }
9842  else
9843  {
9844  TimeToExit = LastTimeToExit;
9845  }
9846  }
9847  else
9848  {
9849  TimeToExit = LastTimeToExit;
9850  }
9851  ExitPair.first = Track->TrackElementAt(1413, LagElement).HLoc;
9852  ExitPair.second = Track->TrackElementAt(1414, LagElement).VLoc;
9853  Utilities->CallLogPop(2342);
9854  return(-1);
9855  }
9856  else
9857  {
9858  TimeToExit = 0; //all train exited
9859  ExitPair.first = Track->TrackElementAt(1415, LagElement).HLoc;
9860  ExitPair.second = Track->TrackElementAt(1416, LagElement).VLoc;
9861  Utilities->CallLogPop(2343);
9862  return(-1);
9863  }
9864  }
9865  if((LeadElement == -1) && (MidElement > -1) && (Track->TrackElementAt(1417, MidElement).TrackType == Continuation)/* && (ExitSpeedFull > 1)*/)
9866  //here MidElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9867  {
9868  if(Straddle == LeadMidLag) //front element of train half off the exit, 1.5 lengths to exit
9869  {
9870  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9871  {
9872  TempTTE = (1.5 * Track->TrackElementAt(1418, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9873  if(TempTTE < LastTimeToExit)
9874  {
9875  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9876  }
9877  else
9878  {
9879  TimeToExit = LastTimeToExit;
9880  }
9881  }
9882  else
9883  {
9884  TimeToExit = LastTimeToExit;
9885  }
9886  ExitPair.first = Track->TrackElementAt(1419, MidElement).HLoc;
9887  ExitPair.second = Track->TrackElementAt(1420, MidElement).VLoc;
9888  Utilities->CallLogPop(2344);
9889  return(-1);
9890  }
9891  else //front element of train fully off the exit, one length to exit
9892  {
9893  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9894  {
9895  TempTTE = (Track->TrackElementAt(1421, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9896  if(TempTTE < LastTimeToExit)
9897  {
9898  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9899  }
9900  else
9901  {
9902  TimeToExit = LastTimeToExit;
9903  }
9904  }
9905  else
9906  {
9907  TimeToExit = LastTimeToExit;
9908  }
9909  ExitPair.first = Track->TrackElementAt(1422, MidElement).HLoc;
9910  ExitPair.second = Track->TrackElementAt(1423, MidElement).VLoc;
9911  Utilities->CallLogPop(2345);
9912  return(-1);
9913  }
9914  }
9915  if(LeadElement > -1)
9916  {
9918  /* && (ExitSpeedFull > 1)*/) //LeadElement is the exit. If LeadMidLag have lead half on exit or if LeadMid have lead full on exit
9919  { //if LeadMidLag have 2.5 lengths to exit, else have 2 lengths to exit
9920  //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9921  if(Straddle == LeadMidLag)
9922  {
9923  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9924  {
9925  TempTTE = (2.5 * Track->TrackElementAt(1426, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9926  if(TempTTE < LastTimeToExit)
9927  {
9928  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9929  }
9930  else
9931  {
9932  TimeToExit = LastTimeToExit;
9933  }
9934  }
9935  else
9936  {
9937  TimeToExit = LastTimeToExit;
9938  }
9939  ExitPair.first = Track->TrackElementAt(1427, LeadElement).HLoc;
9940  ExitPair.second = Track->TrackElementAt(1428, LeadElement).VLoc;
9941  Utilities->CallLogPop(2346);
9942  return(-1);
9943  }
9944  else
9945  {
9946  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9947  {
9948  TempTTE = (2 * Track->TrackElementAt(1429, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9949  if(TempTTE < LastTimeToExit)
9950  {
9951  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9952  }
9953  else
9954  {
9955  TimeToExit = LastTimeToExit;
9956  }
9957  }
9958  else
9959  {
9960  TimeToExit = LastTimeToExit;
9961  }
9962  ExitPair.first = Track->TrackElementAt(1430, LeadElement).HLoc;
9963  ExitPair.second = Track->TrackElementAt(1431, LeadElement).VLoc;
9964  Utilities->CallLogPop(2347);
9965  return(-1);
9966  }
9967  }
9968  }
9969 //here if LeadElement > -1 and its forward connection also > -1
9970 
9971  // calc distance to next red signal
9972  if(!Stopped() || RevisedStoppedAtLoc())
9973  {
9974  int FirstPosToBeMeasured = Track->TrackElementAt(953, LeadElement).Conn[LeadExitPos];
9975  int FirstEntryPos = Track->TrackElementAt(954, LeadElement).ConnLinkPos[LeadExitPos];
9976  if((Straddle == LeadMidLag) && (TrainMode == Timetable))
9977 /* In TTMode it's important to set the first element to be measured ahead of the lead element only when the train fully on
9978  2 elements. Otherwise, if the train is only half on the lead element and approaching a station stop where the platform doesn't
9979  extend beyond the lead element stop point, the element ahead of the lead element is not a location whereas the ActionVector
9980  still points to the station stop location. In these circumstances the train hasn't yet stopped, so the dwell time at the
9981  stop isn't calculated, and the station to be stopped at isn't found as a future stop and nor are any other future stops
9982  because the ActionVector name never matches a future station. Hence all dwell times are omitted until the train lands fully
9983  on two elements. To avoid this when Straddle is LeadMidLag the first element to be measured is set to the lead element, so
9984  before the train has stopped the current station is still recognised as a future stop.
9985  In signaller mode stops don't count, and if pass stop signal command is given then when have LeadMidLag the current element
9986  becomes the signal, and the time to act indication becomes 'NOW'.
9987 */
9988  {
9989  FirstPosToBeMeasured = LeadElement;
9990  FirstEntryPos = LeadEntryPos;
9991  }
9992  float CurrentStopTime; // set to 0 at start of function
9993  float LaterStopTime; // set to 0 at start of function
9994  float RecoverableTime; // set to 0 at start of function
9995  int AvTrackSpeed; // set to zero at start of function
9996  bool SigControlAndCanPassRedSignal = ((TrainMode == Signaller) && AllowedToPassRedSignal);
9997  DistanceToRedSignal = TrainController->CalcDistanceToRedSignalandStopTime(0, FirstPosToBeMeasured, FirstEntryPos, SigControlAndCanPassRedSignal,
9998  ActionVectorEntryPtr, HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
9999 //at this point can't have both DistanceToRedSignal and DistanceToExit both set. Either both will be unset (-1) or one will be set.
10000 //Therefore since need to calculate the time for each in the same way use a generic
10001 
10002  if((DistanceToRedSignal == -1) && (DistanceToExit == -1))// both unset so no action needed
10003  {
10004  TimeToExit = -1;
10005  Utilities->CallLogPop(2076);
10006  return(-1);
10007  }
10008 //else one or other is set
10009  bool DistanceToRedSignalSet = (DistanceToRedSignal > -1);
10010  bool DistanceToExitSet = (DistanceToExit > -1);
10011  int GenericDistance = DistanceToRedSignal;
10012  if(DistanceToExitSet)
10013  {
10014  GenericDistance = DistanceToExit;
10015  }
10016 /* Have MinsDelayed; pos or neg,
10017  CurrentStopTime; pos or zero
10018  LaterStopTime; pos or zero
10019  RecoverableTime; pos or zero
10020 
10021  & from these calculate TotalStopTime. noting that:
10022  If stopped CurrentStopTime automatically adjusts for all early running and for as much late running as possible
10023  RecoverableTime always < LaterStopTime or both zero
10024  can't subtract more than RecoverableTime (MinsDelayed > 0)
10025  only subtract from LaterStopTime, not CurrentTime (MinsDelayed > 0)
10026  only subtract from LaterStopTime if LaterStopTime > 0 (MinsDelayed > 0)
10027  only add to LaterStopTime if LaterStopTime > 0 (MinsDelayed < 0)
10028  if running early & stopped at location CurrentStopTime will automatically include the excess
10029 */
10030  float TimeToSubtract, TotalStopTime;
10031  if(MinsDelayed > RecoverableTime)
10032  {
10033  TimeToSubtract = RecoverableTime;
10034  }
10035  else
10036  {
10037  TimeToSubtract = MinsDelayed; // may be negative;
10038  }
10039  if((AvTrackSpeed > 0) && (DistanceToStationStop <= GenericDistance) && (DistanceToStationStop > 0)) //protection against div by zero, not needed of no stop
10040  //before red signal, DistanceToStationStop != 0 as set to 0 if invalid
10041  //added at v2.6.1, DistanceToStationStop is calculated in SetTrainMovementValues, AvTrackSpeed is average to next red signal, but should be ok to use for
10042  //next station stop
10043  //after 2.7.0 changed (DistanceToStationStop < DistanceToRedSignal) to (DistanceToStationStop <= DistanceToRedSignal) because often have departure signal
10044  //next to the stop platform and if so the two distances are the same and the station stop time isn't included. Also after 2.7.0 used GetRepeatTime... to calc
10045  //departure time because ActionVectorEntryPtr->DepartureTime is the base time and therefore incorrect for repeats.
10046  //first find departure time from the next stop
10047  //at v2.9.0 changed MinsEarly calc to use ArrivalTime instead of departure time
10048  {
10049  if(ActionVectorEntryPtr->FormatType == TimeTimeLoc) //if already arrived then MinsEarly will be < 0 so becomes set to 0
10050  {
10053  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
10054  }
10055  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime != TDateTime(-1))) // not arrived yet
10056  {
10058  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
10059  if((ActionVectorEntryPtr + 1)->FormatType == TimeLoc)
10060  {
10061  // must be a departure
10062  DepartureTime = TrainController->GetRepeatTime(70, (ActionVectorEntryPtr + 1)->DepartureTime, RepeatNumber, IncrementalMinutes);
10063  }
10064  }
10065  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime == TDateTime(-1))) //already arrived
10066  {
10067  MinsEarly = 0;
10068  }
10069  if(MinsEarly < 0)
10070  {
10071  MinsEarly = 0;
10072  }
10073  }
10074  if(MinsDelayed < 0) // MinsDelayed < 0 means have arrived early at a station
10075  {
10076  if(CurrentStopTime > 0)
10077  {
10078  TotalStopTime = CurrentStopTime + LaterStopTime;
10079  }
10080  // stopped at loc, will depart on time
10081  else
10082  {
10083  TotalStopTime = LaterStopTime - MinsDelayed;
10084  }
10085  // not stopped, will depart on time at first later stop so add the delay
10086  }
10087  else if((MinsEarly > 0) && !Stopped()) //running early
10088  {
10089  TotalStopTime = LaterStopTime + MinsEarly;
10090  }
10091  else // on time or running late
10092  {
10093  if(LaterStopTime == 0)
10094  {
10095  TotalStopTime = CurrentStopTime;
10096  }
10097  // no later stops, if stopped now will depart as soon as possible,
10098  // if not stopped no stop times to add
10099  else
10100  {
10101  TotalStopTime = CurrentStopTime + LaterStopTime - TimeToSubtract; // later stops so deduct as much as can
10102  }
10103  }
10104  if(AvTrackSpeed < 30)
10105  {
10106  AvTrackSpeed = 30;
10107  }
10108  int Speed = AvTrackSpeed;
10109  if(AvTrackSpeed > int(MaxRunningSpeed))
10110  {
10111  Speed = int(MaxRunningSpeed);
10112  }
10113  if(TrainMode == Signaller)
10114  {
10115  Speed = SignallerMaxSpeed;
10116  TotalStopTime = 0;
10117  }
10118  if(DistanceToRedSignalSet)
10119  {
10120  TimeToAct = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
10121  // accel & decel taken into account in
10122  // CalcDistanceToRedSignalandStopTime
10123  // 3.6 converts Km/h to m/s & 60 converts seconds to minutes
10124  TimeToExit = -1;
10125  Utilities->CallLogPop(2079);
10126  return(TimeToAct);
10127  }
10128  else //DistanceToExitSet must be true
10129  {
10130  TimeToExit = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
10131  // accel & decel taken into account in
10132  // CalcDistanceToRedSignalandStopTime
10133  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
10134  Utilities->CallLogPop(2370);
10135  return(-1); //no red signal so no time to act
10136  }
10137  }
10138  else // stopped not at location
10139  {
10141  {
10142  TimeToAct = 0.0;
10143  TimeToExit = -1;
10144  }
10145  if(StoppedWithoutPower) //added at v2.13.2 as this situation was missed & time to act was 0 [If train failed then covered above]
10146  {
10147  TimeToAct = -1;
10148  TimeToExit = -1;
10149  }
10150  // but if stopped at a signal & autosigs route after it then ok, provided signal not failed
10151  if(StoppedAtSignal)
10152  {
10153  int NextElement = Track->TrackElementAt(928, LeadElement).Conn[LeadExitPos];
10154  int NextEntryPos = Track->TrackElementAt(929, LeadElement).ConnLinkPos[LeadExitPos];
10155  bool NextElementFailed = Track->TrackElementAt(1548, NextElement).Failed; //added at v2.13.2
10156  int NextExitPos;
10157  if(Track->TrackElementAt(930, NextElement).TrackType == Points)
10158  {
10159  if((NextEntryPos == 0) || (NextEntryPos == 2))
10160  // leading entry point
10161  {
10162  if(Track->TrackElementAt(931, NextElement).Attribute == 0)
10163  {
10164  NextExitPos = 1;
10165  }
10166  else
10167  {
10168  NextExitPos = 3;
10169  }
10170  }
10171  else
10172  {
10173  NextExitPos = 0; // trailing entry point
10174  }
10175  }
10176  else
10177  {
10178  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
10179  }
10180  int NextButOneElement = Track->TrackElementAt(932, NextElement).Conn[NextExitPos];
10181  int NextButOneEntryPos = Track->TrackElementAt(933, NextElement).ConnLinkPos[NextExitPos];
10182  int RouteNumber; // holder for referenced value, not used
10183  if((AllRoutes->GetRouteTypeAndNumber(32, NextButOneElement, NextButOneEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !NextElementFailed)
10184  { //NextElementFailed added at v2.13.2
10185  TimeToAct = -1;
10186  TimeToExit = -1;
10187  }
10188  }
10189  Utilities->CallLogPop(2074);
10190  return(TimeToAct);
10191  }
10192 }
10193 
10194 // ---------------------------------------------------------------------------
10195 
10197 {
10198  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainOnContinuation, " + HeadCode);
10199  if(LeadElement > -1)
10200  {
10202  {
10203  Utilities->CallLogPop(2148);
10204  return(true);
10205  }
10206  }
10207  if(MidElement > -1)
10208  {
10210  {
10211  Utilities->CallLogPop(2149);
10212  return(true);
10213  }
10214  }
10215  if(LagElement > -1)
10216  {
10218  {
10219  Utilities->CallLogPop(2150);
10220  return(true);
10221  }
10222  }
10223  Utilities->CallLogPop(2151);
10224  return(false);
10225 }
10226 
10227 // ---------------------------------------------------------------------------
10228 
10229 // ---------------------------------------------------------------------------
10230 // TTrainController
10231 // ---------------------------------------------------------------------------
10232 
10234 { //this called from ClearEverything() so called whenever load railway or load session, so whenever background changed it is called
10235  OnTimeArrivals = 0;
10236  LateArrivals = 0;
10237  EarlyArrivals = 0;
10238  OnTimePasses = 0;
10239  LatePasses = 0;
10240  EarlyPasses = 0;
10241  OnTimeExits = 0; //these 3 exits added at v2.9.2 - missed in error earlier
10242  LateExits = 0;
10243  EarlyExits = 0;
10244  OnTimeDeps = 0;
10245  LateDeps = 0;
10246  MissedStops = 0;
10247  OtherMissedEvents = 0;
10248  UnexpectedExits = 0;
10249  NumFailures = 0;
10251  AvHoursIntValue = 0;
10252  MTBFHours = 0;
10254  IncorrectExits = 0;
10255  SPADEvents = 0;
10256  SPADRisks = 0;
10257  CrashedTrains = 0;
10258  Derailments = 0;
10259  TotArrDepPass = 0;
10260  TotLateArrMins = 0;
10261  TotEarlyArrMins = 0;
10262  TotLatePassMins = 0;
10263  TotEarlyPassMins = 0;
10264  TotLateExitMins = 0; //added at v2.9.1
10265  TotEarlyExitMins = 0; //added at v2.9.1
10266  TotLateDepMins = 0;
10267  ExcessLCDownMins = 0;
10268  SkippedTTEvents = 0; //added at v2.11.0
10269  TTClockTime = 0; // added for v0.6
10271  // added at v1.3.0 to ensure false at start
10272  OpTimeToActUpdateCounter = 0; // new v2.2.0
10273  OpActionPanelVisible = false; // new v2.2.0
10274  // reset all message flags, stops them being given twice (shouldn't be needed here but add for safety) //new at v2.4.0
10275  SSHigh = false;
10276 // MRSHigh = false; removed at v2.21.0
10277 // MRSLow = false;
10278  MassHigh = false;
10279  BFHigh = false;
10280  BFLow = false;
10281  PwrHigh = false;
10282  SigSHigh = false;
10283  SigSLow = false;
10284  randomize();
10285  // to seed rand() & random() with a random number (see UpdateTrain)
10286  LongServRefFont = new TFont;
10287  LongServRefFont->Name = "Arial";
10288  LongServRefFont->Style = TFontStyles() << fsBold;
10289  LongServRefFont->Size = 8;
10290  if(Utilities->clTransparent == clB5G5R5) //white
10291  {
10292  LongServRefFont->Color = clB1G0R0; //very dark blue
10293  LongServRefFontColNumber = 0x01; //clB1G0R0
10294  NearTransparentColNumber = 0xda; // =218 decimal, was 0xd6; //clB4G5R5 - see RailGraphics->SetWebSafePalette(...
10295  BgndColNumber = 0xd7; //clB5G5R5
10296  }
10297  else if(Utilities->clTransparent == clB0G0R0) //black
10298  {
10299  LongServRefFont->Color = clB3G5R5; //cream //number 0xd5
10300  LongServRefFontColNumber = 0xd5; //clB3G5R5
10301  NearTransparentColNumber = 0xd8; // = 216 decimal, was 0x01; //clB1G0R0 - see RailGraphics->SetWebSafePalette(...
10302  BgndColNumber = 0x00; //clB0G0R0
10303  }
10304  else //clB1G0R0) dark blue
10305  {
10306  LongServRefFont->Color = clB3G5R5; //cream
10307  LongServRefFontColNumber = 0xd5; //clB3G5R5
10308  NearTransparentColNumber = 0xd9; // =217 decimal,was 0x00; //clB0G0R0 - see RailGraphics->SetWebSafePalette(...
10309  BgndColNumber = 0x01; //clB1G0R0
10310  }
10311 }
10312 
10313 // ---------------------------------------------------------------------------
10314 
10316 {
10317  for(unsigned int x = 0; x < TrainVector.size(); x++)
10318  {
10319  TrainVectorAt(32, x).DeleteTrain(4);
10320  }
10321  TrainVector.clear();
10322  delete LongServRefFont;
10323 }
10324 
10325 // ---------------------------------------------------------------------------
10326 
10327 void TTrainController::LogEvent(AnsiString Str)
10328 {
10329  AnsiString FullStr = Utilities->TimeStamp() + "," + TTClockTime.FormatString("hh:nn:ss") + "," + Str;
10330 
10331  // restrict to last 1000 entries
10332  Utilities->EventLog.push_back(FullStr);
10333  if(Utilities->EventLog.size() > 1000)
10334  {
10335  Utilities->EventLog.pop_front();
10336  }
10337 }
10338 
10339 // ---------------------------------------------------------------------------
10340 
10342 {
10343  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Operate");
10344  bool ClockState = Utilities->Clock2Stopped;
10345  Utilities->Clock2Stopped = true;
10346  // new section dealing with Snt & Snt-sh additions
10347  // BUT don't add trains if points or route flashing [conditions added for Version 0.6 as a result of Najamuddin's error - 15/01/11] - wait until next
10348  // clock tick after stops flashing
10350  {
10351  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10352  {
10353  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
10354  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
10355  TActionEventType EventType = NoEvent;
10356  if(AVEntry0.Command == "Snt")
10357  {
10358  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10359  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10360  int IncrementalMinutes = 0;
10361  int IncrementalDigits = 0;
10362  if(AVEntryLast.FormatType == Repeat)
10363  {
10364  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10365  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10366  }
10367  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10368  {
10369  throw Exception("Error - Repeat entry && less than two trains for Snt entry: " + TDEntry.HeadCode);
10370  }
10371  // see above note
10372 
10373  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10374  {
10375  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
10376  if(TTOD.RunningEntry != NotStarted)
10377  {
10378  continue;
10379  }
10380 
10381 //Multiplayer: here check for a train entering at a coupling {RearStartOrRepeatMins shows if it's a coupling or not), and can only be a Snt entry
10382 //if so and no arrival signalled yet bypass the timetabled arrival
10383 //if so and arrival signalled then start the new service, using the repeat number and headcode for the entering train
10384 //if a repeat is skipped then should be ok if it arrives later as its RunningEntry is still NotStarted
10385 
10386  if(GetRepeatTime(2, AVEntry0.EventTime, y, IncrementalMinutes) > TTClockTime)
10387  {
10388  break; // all the rest will also be greater
10389  }
10390  AnsiString TrainHeadCode = GetRepeatHeadCode(22, TDEntry.HeadCode, y, IncrementalDigits);
10391  if(AddTrain(2, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TrainHeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10392  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, y, IncrementalMinutes, IncrementalDigits,
10393  TDEntry.SignallerSpeed, AVEntry0.SignallerControl, EventType))
10394  {
10395  TTOD.TrainID = TrainVector.back().TrainID;
10396  TTOD.RunningEntry = Running;
10397  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10398  }
10399  else if(EventType == FailTrainEntry)
10400  {
10401  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10402  }
10403  }
10404  }
10405  if(AVEntry0.Command == "Snt-sh")
10406  // just start this once, shuttle repeats take care of restarts
10407  {
10408  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10409  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10410  int IncrementalMinutes = 0;
10411  int IncrementalDigits = 0;
10412  if(AVEntryLast.FormatType == Repeat)
10413  {
10414  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10415  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10416  }
10417  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10418  {
10419  throw Exception("Error - Repeat entry && less than two trains for Snt-sh entry: " + TDEntry.HeadCode);
10420  }
10421  // see above note
10422  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(0);
10423  if(TTOD.RunningEntry == NotStarted)
10424  {
10425  if(AVEntry0.EventTime <= TTClockTime)
10426  {
10427  if(AddTrain(3, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TDEntry.HeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10428  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, 0, IncrementalMinutes, IncrementalDigits,
10429  TDEntry.SignallerSpeed, false, EventType))
10430  // false for SignallerControl
10431  {
10432  TTOD.TrainID = TrainVector.back().TrainID;
10433  TTOD.RunningEntry = Running;
10434  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10435  }
10436  else if(EventType == FailTrainEntry)
10437  {
10438  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10439  }
10440  }
10441  }
10442  }
10443  }
10444  }
10445 
10446  // deal with running trains but abort if any vectors added, would probably be OK but don't risk a vector reallocation disrupting the
10447  // iteration, next cycle will catch up with any other pending updates
10448  if(!TrainVector.empty())
10449  {
10450  TrainAdded = false;
10451 
10452 //elapsed time investigations
10453 
10454 //elapsed time segment
10455 //double Start, End;
10456 //AnsiString ElapsedTimeReport = "";
10457 //end elasped time segment
10458  AllRoutes->CallonVector.clear(); // this will be rebuilt during the calls to UpdateTrain
10459 //elapsed time segment
10460 //PerfLogForm->PerformanceLog(-1, "\n Train vector size: " + AnsiString(TrainVector.size()) + '\n');
10461 //PerfLogForm->PerformanceLog(-1, "Start time list");
10462 //end elapsed time segment
10463  for(unsigned int x = 0; x < TrainVector.size(); x++)
10464  {
10465 //elapsed time segment
10466 //Start = double(GetTime()) * 86400; //secs
10467 //end elapsed time segment
10468  TrainVectorAt(33, x).UpdateTrain(0);
10469 //elapsed time segment
10470 //End = double(GetTime()) * 86400;
10471 //ElapsedTimeReport = TrainVectorAt(-1, x).TrainDataEntryPtr->ServiceReference + AnsiString(" ") + AnsiString(int((End - Start) * 1000)); //msecs
10472 //PerfLogForm->PerformanceLog(-1, ElapsedTimeReport);
10473 //end elapsed time segment
10474 
10475 //end elapsed time investigations
10476 
10477  /* added HasTrainGone() condition below in v0.4c to prevent 2 trains both having TrainGone set in UpdateTrain
10478  at the same time. That caused the error Craig Weekes reported in November 2010 where 2 trains exited at the same time, and later the TrainVector
10479  iterates in reverse to erase the second train to have gone (when the first train to have gone comes before the second in TrainVector),
10480  but afterwards ReplotTrains iterates forwards and therefore replots the first train to have gone and therefore sets the TrainIDOnElement value
10481  to the exited train, with nothing to reset it. Hovering the mouse over that element with train information enabled causes an error because
10482  the track element thinks the train is still there, whereas it is missing from the TrainVector. BUT subsequently (in v2.11.1) changed RePlotTrains
10483  so it doesn't plot trains with TrainGone set, but left this is as does no harm
10484 
10485  Had another error notified by Kevin Smith on 02/01/22 where a train was manually removed in the same clock cycle as a train exited, and this caused
10486  the same error as above. Did a lot of experimenting but eventually cured it with two changes, first as above in RePlotTrains, and also below adding
10487  a break; command after one TrainHasGone() dealt with. There were introduced in v2.11.1 & seems ok now
10488 
10489  These changes should deal with any number of TrainGone flags set in the same clock cycle - from exiting, manual removal, or joins
10490  */
10491  if(TrainAdded || TrainVectorAt(35, x).HasTrainGone())
10492  {
10493  break; //only one exited train will be dealt with at a time (see below) so no point looking further
10494  }
10495  }
10496  // set warning flags (ManualLCDownAttentionWarning dealt with in InterfaceUnit)
10497  CrashWarning = false;
10498  DerailWarning = false;
10499  SPADWarning = false;
10500  CallOnWarning = false;
10501  SignalStopWarning = false;
10502  BufferAttentionWarning = false;
10503  TrainFailedWarning = false;
10504  for(int x = TrainVector.size() - 1; x >= 0; x--) // reverse because of erase
10505  {
10506  TTrain &Train = TrainVectorAt(34, x);
10507  if(Train.Crashed)
10508  // can't use background colours for crashed & derailed because same colour
10509  {
10510  CrashWarning = true;
10511  }
10512  else if(Train.Derailed)
10513  // can't use background colours for crashed & derailed because same colour
10514  {
10515  DerailWarning = true;
10516  }
10517  else if(Train.BackgroundColour == clSPADBackground)
10518  // use colour as that changes as soon as passes signal
10519  {
10520  SPADWarning = true;
10521  }
10522  else if(Train.BackgroundColour == clTrainFailedBackground)
10523  {
10524  TrainFailedWarning = true;
10525  }
10526  else if(Train.BackgroundColour == clCallOnBackground)
10527  // use colour as also stopped at signal
10528  {
10529  CallOnWarning = true;
10530  }
10531  else if(Train.BackgroundColour == clSignalStopBackground)
10532  // use colour to distinguish from call-on
10533  {
10534  SignalStopWarning = true;
10535  }
10536  else if(Train.BackgroundColour == clBufferAttentionNeeded)
10537  // use colour to distinguish from ordinary buffer stop
10538  {
10539  BufferAttentionWarning = true;
10540  }
10541  if(Train.HasTrainGone())
10542  {
10543  AnsiString Loc = "";
10544  bool ElementFound = false;
10545  TTrackElement TE;
10546  if(Train.LagElement > -1)
10547  {
10548  TE = Track->TrackElementAt(531, Train.LagElement);
10549  ElementFound = true;
10550  }
10551  else if(Train.MidElement > -1)
10552  {
10553  TE = Track->TrackElementAt(779, Train.MidElement);
10554  ElementFound = true;
10555  }
10556  else if(Train.LeadElement > -1)
10557  {
10558  TE = Track->TrackElementAt(780, Train.LeadElement);
10559  ElementFound = true;
10560  }
10561  if(ElementFound)
10562  {
10563  if(TE.ActiveTrackElementName != "")
10564  {
10565  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10566  }
10567  else
10568  {
10569  Loc = "track element " + TE.ElementID;
10570  }
10571  }
10572  TActionVectorEntry *AVEntryPtr = Train.ActionVectorEntryPtr;
10573  if((Train.SignallerRemoved) || (Train.JoinedOtherTrainFlag))
10574  // need above first because may also have ActionVectorEntryPtr == "Fer"
10575  {
10576  Train.UnplotTrain(9);
10577  // added at v1.3.0 to reset signals after train removed from an autosigsroute
10579  {
10582  }
10583  // end of addition
10584  AllRoutes->RebuildRailwayFlag = true;
10585  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot LCs
10586  // correctly after a crash
10587  }
10588  else if(AVEntryPtr->Command == "Fer")
10589  {
10590  bool CorrectExit = false;
10591  if(!AVEntryPtr->ExitList.empty())
10592  {
10593  for(TNumListIterator ELIT = AVEntryPtr->ExitList.begin(); ELIT != AVEntryPtr->ExitList.end(); ELIT++)
10594  {
10595  if(*ELIT == Train.LagElement)
10596  {
10597  CorrectExit = true;
10598  }
10599  }
10600  }
10601  if(CorrectExit)
10602  {
10603  Train.LogAction(19, Train.HeadCode, "", Leave, Loc, "", AVEntryPtr->EventTime, AVEntryPtr->Warning);
10604  }
10605  else
10606  {
10607  LogActionError(38, Train.HeadCode, "", FailIncorrectExit, Loc);
10608  }
10609  }
10610  else
10611  {
10612  if(!AVEntryPtr->SignallerControl)
10613  {
10614  LogActionError(26, Train.HeadCode, "", FailUnexpectedExitRailway, Loc);
10615  Train.SendMissedActionLogs(2, -2, AVEntryPtr);
10616  // -2 is marker for send messages for all remaining actions except Fer if present
10617  }
10618  else
10619  {
10620  Train.LogAction(31, Train.HeadCode, "", SignallerLeave, Loc, "", TDateTime(0), false); // false for Warning
10621  }
10622  }
10623  Utilities->CumulativeDelayedRandMinsAllTrains += Train.CumulativeDelayedRandMinsOneTrain; //added at v2.13.0 for random delays
10624  Train.TrainDataEntryPtr->TrainOperatingDataVector.at(Train.RepeatNumber).RunningEntry = Exited;
10625  Train.DeleteTrain(1);
10626  TrainVector.erase(TrainVector.begin() + x);
10627  ReplotTrains(1, Display); //to reset ElementIDs for remaining trains when have removed a train
10628  //NB: won't plot any trains with TrainGone flag set (changed at v2.11.1)
10629  break; //added at v2.11.1 to ensure that only one train with TrainGone set is dealt with in one clock cycle
10630  }
10631  }
10632  }
10633  else
10634  {
10635  // reset all flags in case last train removed with flag set
10636  CrashWarning = false;
10637  DerailWarning = false;
10638  SPADWarning = false;
10639  CallOnWarning = false;
10640  SignalStopWarning = false;
10641  BufferAttentionWarning = false;
10642  TrainFailedWarning = false;
10643  }
10644  // update OpTimeToActMultimap
10646  {
10648  // clears entries then adds values for running trains then for continuation entries
10650  //added for multiplayer for running trains only
10651  }
10652  Utilities->Clock2Stopped = ClockState;
10653  Utilities->CallLogPop(723);
10654 }
10655 
10656 // ---------------------------------------------------------------------------
10658 {
10659  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishedOperation");
10660  if(!TrainVector.empty())
10661  {
10662  for(int x = TrainVector.size() - 1; x >= 0; x--)
10663  {
10664  TrainVectorAt(50, x).DeleteTrain(2);
10665  }
10666  TrainVector.clear();
10667  }
10668  if(!TrainDataVector.empty())
10669  {
10670  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10671  {
10672  TTrainDataEntry &TDEntry = TrainDataVector.at(x);
10673  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10674  {
10675  TTrainOperatingData &TOD = TDEntry.TrainOperatingDataVector.at(y);
10676  TOD.RunningEntry = NotStarted;
10677  TOD.TrainID = -1;
10678  TOD.EventReported = NoEvent;
10679  }
10680  }
10681  }
10682  Display->GetOutputLog1()->Caption = "";
10683  Display->GetOutputLog2()->Caption = "";
10684  Display->GetOutputLog3()->Caption = "";
10685  Display->GetOutputLog4()->Caption = "";
10686  Display->GetOutputLog5()->Caption = "";
10687  Display->GetOutputLog6()->Caption = "";
10688  Display->GetOutputLog7()->Caption = "";
10689  Display->GetOutputLog8()->Caption = "";
10690  Display->GetOutputLog9()->Caption = "";
10691  Display->GetOutputLog10()->Caption = "";
10692  Utilities->CallLogPop(1352);
10693 }
10694 
10695 // ---------------------------------------------------------------------------
10696 
10698 {
10699  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ReplotTrains");
10700  if(!TrainVector.empty())
10701  {
10702  for(unsigned int x = 0; x < TrainVector.size(); x++)
10703  {
10704  if(!TrainVectorAt(84, x).HasTrainGone()) //added at v2.11.0 to prevent plotting a train pending removal & particularly to prevent TrainElementID's being reinstated
10705  { //see Kevin Smith error information for details
10706  TrainVectorAt(51, x).PlotTrain(4, Disp);
10707  }
10708  }
10709  }
10710  Utilities->CallLogPop(724);
10711 }
10712 
10713 // ---------------------------------------------------------------------------
10714 
10715 void TTrainController::WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
10716 {
10717  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainsToImage");
10718  if(!TrainVector.empty())
10719  {
10720  for(unsigned int x = 0; x < TrainVector.size(); x++)
10721  {
10722  TrainVectorAt(61, x).WriteTrainToImage(0, Bitmap);
10723  }
10724  }
10725  Utilities->CallLogPop(1707);
10726 }
10727 
10728 // ---------------------------------------------------------------------------
10729 
10731 {
10732  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrains");
10733  if(!TrainVector.empty())
10734  {
10735  for(unsigned int x = 0; x < TrainVector.size(); x++)
10736  {
10737  TrainVectorAt(52, x).UnplotTrain(10);
10738  }
10739  }
10741  Utilities->CallLogPop(725);
10742 }
10743 
10744 // ---------------------------------------------------------------------------
10745 
10746 bool TTrainController::AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed,
10747  double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes,
10748  int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
10749 {
10750  LogEvent(AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) + "," + HeadCode + "," + AnsiString(StartSpeed) +
10751  "," + AnsiString(Mass) + "," + ModeStr);
10752  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) +
10753  "," + HeadCode + "," + AnsiString(StartSpeed) + "," + AnsiString(Mass) + "," + ModeStr); //at v2.11.1 dropped later headcode - was listed twice
10754 
10755  int RearExitPos = -1;
10756 
10757  for(int x = 0; x < 4; x++)
10758  {
10759  if(Track->TrackElementAt(519, RearPosition).Conn[x] == FrontPosition)
10760  {
10761  RearExitPos = x;
10762  }
10763  }
10764  if(RearExitPos == -1)
10765  {
10766  throw Exception("Error, RearExit == -1 in AddTrain");
10767  }
10768  bool ReportFlag = true;
10769 
10770  // used to stop repeated messages from CheckStartAllowable when split failed
10771  if(TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported != NoEvent)
10772  {
10773  ReportFlag = false;
10774  }
10775  if(!CheckStartAllowable(0, RearPosition, RearExitPos, HeadCode, ReportFlag, EventType))
10776  {
10777  // messages sent to performance log in CheckStartAllowable if ReportFlag true
10778  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = EventType;
10779  Utilities->CallLogPop(938);
10780  return(false);
10781  }
10782  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
10783  TTrainMode TrainMode = NoMode;
10784 
10785  if(ModeStr == "Timetable")
10786  {
10787  TrainMode = Timetable;
10788  }
10789  // all else gives 'None', 'Signaller' set within program
10790 
10791  if(MaxRunningSpeed < 10)
10792  {
10793  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
10794  }
10795  if(SignallerSpeed < 10)
10796  {
10797  SignallerSpeed = 10; // added at v0.6 to avoid low max speeds
10798  }
10799  TTrain *NewTrain = new TTrain(0, RearPosition, RearExitPos, HeadCode, StartSpeed, Mass, MaxRunningSpeed, MaxBrakeRate, PowerAtRail, TrainMode,
10800  TrainDataEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerSpeed);
10801 
10802  LogEvent("AddTrainSupplemental: Service Ref = " + TrainDataEntryPtr->ServiceReference + ", TrainID = " + AnsiString(NewTrain->TrainID)); //new at v2.11.1 so can relate headcode to ID
10803 
10804  NewTrain->ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0));
10805  // initialise here rather than in TTrain constructor as create trains
10806  // with Null TrainDataEntryPtr when loading session trains
10807  if(SignallerControl)
10808  {
10809  NewTrain->TimetableFinished = true;
10810  NewTrain->SignallerStoppingFlag = false;
10811  NewTrain->TrainMode = Signaller;
10812  if(NewTrain->MaxRunningSpeed > NewTrain->SignallerMaxSpeed)
10813  {
10814  NewTrain->MaxRunningSpeed = NewTrain->SignallerMaxSpeed;
10815  }
10816  //added at v2.23.2 so sig train with zero start speed is stopped on creation
10817  NewTrain->SignallerStopped = false;
10818  if(NewTrain->TrainDataEntryPtr->StartSpeed == 0)
10819  {
10820  NewTrain->SignallerStopped = true;
10821  }
10822  //end of addition
10824  }
10825  // deal with starting conditions:-
10826  // unlocated Snt: just report entry & advance pointer
10827  // located Snt or Sfs: set station conditions as would if had reached stop point in Update(), & advance the ActionVectorEntryPtr
10828  // Sns doesn't need a new train
10829  if(NewTrain->ActionVectorEntryPtr->LocationName != "")
10830  // covers all above located starts
10831  // if location of Snt was a station (that is set as LocationName, i.e. not just any station) that isn't next departure station then
10832  // wouldn't have accepted the timetable
10833  {
10834  // first check if LeadElement (can't access LeadElement directly yet as not set, use FrontPosition instead) is buffers, note that
10835  // StoppedAtBuffers is set in UpdateTrain()
10836  if(Track->TrackElementAt(520, FrontPosition).TrackType == Buffers)
10837  // buffer end must be ahead of train or would have failed start position check
10838  {
10839  NewTrain->StoppedAtLocation = true;
10840  NewTrain->PlotStartPosition(0);
10842  NewTrain->LogAction(20, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10843  NewTrain->ActionVectorEntryPtr->Warning);
10844  if(!SignallerControl) // don't advance if SignalControlEntry
10845  {
10846  NewTrain->ActionVectorEntryPtr++;
10847  // should be a command, could be a location departure but if so can't depart so set 'Hold' anyway
10848  }
10849  NewTrain->LastActionTime = TTClockTime;
10850  }
10851  // else a through station stop
10852  else
10853  {
10854  NewTrain->StoppedAtLocation = true;
10855  NewTrain->PlotStartPosition(10);
10857  NewTrain->LogAction(21, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10858  NewTrain->ActionVectorEntryPtr->Warning);
10859  if(!SignallerControl) // don't advance if SignalControlEntry
10860  {
10861  NewTrain->ActionVectorEntryPtr++;
10862  }
10863  NewTrain->LastActionTime = TTClockTime;
10864  }
10865  }
10866  else // unlocated entry (i.e. not a stop entry, but could still be at a named location)
10867  {
10868  NewTrain->PlotStartPosition(11);
10869  TTrackElement TE = Track->TrackElementAt(530, NewTrain->RearStartElement);
10870  AnsiString Loc = "";
10871  if(TE.ActiveTrackElementName != "")
10872  {
10873  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10874  }
10875  else
10876  {
10877  Loc = "track element " + TE.ElementID;
10878  }
10879  if(TE.TrackType == Continuation)
10880  {
10881  NewTrain->LogAction(22, NewTrain->HeadCode, "", Enter, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10882  }
10883  else
10884  {
10885  NewTrain->LogAction(23, NewTrain->HeadCode, "", Create, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10886  }
10887  if(!SignallerControl) // don't advance if SignalControlEntry
10888  {
10889  NewTrain->ActionVectorEntryPtr++;
10890  }
10891  NewTrain->LastActionTime = TTClockTime;
10892  // no need to set LastActionTime for an unlocated entry
10893  }
10894  // cancel a wrong-direction route if either element of train starts on one
10895  if(NewTrain->LeadElement > -1)
10896  {
10897  NewTrain->CheckAndCancelRouteForWrongEndEntry(3, NewTrain->LeadElement, NewTrain->LeadEntryPos);
10898  }
10899  if(NewTrain->MidElement > -1)
10900  {
10901  NewTrain->CheckAndCancelRouteForWrongEndEntry(4, NewTrain->MidElement, NewTrain->MidEntryPos);
10902  }
10903  // set signals for a right-direction autosigs route for either element of train on one
10904  // erase elements back to start for a non-autosigs route & check if an autosigs route immediately behind it, and if so set its signals
10905  // note that all but autosigs routes become part of a single route, so there can only be an autosigs route behind the non-autosigs route
10906  int RouteNumber = -1;
10907  bool SignalsSet = false;
10908 
10909  if(NewTrain->LeadElement > -1)
10910  {
10911  if(AllRoutes->GetRouteTypeAndNumber(13, NewTrain->LeadElement, NewTrain->LeadEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10912  {
10913  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10914  int RouteStartPosition;
10915  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10916  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(21, Track->TrackElementAt(955, FrontPosition).HLoc,
10917  Track->TrackElementAt(956, FrontPosition).VLoc, SecondPair);
10918  if(FirstPair.first == RouteNumber)
10919  {
10920  RouteStartPosition = FirstPair.second;
10921  }
10922  else if(SecondPair.first == RouteNumber)
10923  {
10924  RouteStartPosition = SecondPair.second;
10925  }
10926  else
10927  {
10928  throw Exception("Error, RouteNumber not found in Route2MultiMap in 1st of 2 calls to SetAllRearwardsSignals in AddTrain");
10929  }
10930  AllRoutes->SetAllRearwardsSignals(10, 0, RouteNumber, RouteStartPosition);
10931  SignalsSet = true;
10932  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10933  }
10934  else if(RouteNumber > -1) // non-autosigsroute
10935  {
10936  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(181, RouteNumber).GetFixedPrefDirElementAt(194, 0);
10937  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10938  int FirstELinkPos = TempPDE.GetELinkPos();
10939  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->LeadElement))
10940  {
10941  AllRoutes->RemoveRouteElement(16, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10942  TempPDE = AllRoutes->GetFixedRouteAt(182, RouteNumber).GetFixedPrefDirElementAt(195, 0);
10943  }
10944  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->LeadElement))
10945  {
10946  AllRoutes->RemoveRouteElement(17, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10947  // remove the last element under LeadElement
10948  }
10949  AllRoutes->RebuildRailwayFlag = true;
10950  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10951  // now deal with a rear linked autosigs route
10952  if(Track->TrackElementAt(820, FirstTVPos).Conn[FirstELinkPos] > -1)
10953  {
10954  int LinkedRouteNumber = -1;
10955  if(AllRoutes->GetRouteTypeAndNumber(17, Track->TrackElementAt(821, FirstTVPos).Conn[FirstELinkPos],
10956  Track->TrackElementAt(822, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10957  {
10958  AllRoutes->GetFixedRouteAt(169, LinkedRouteNumber).SetRouteSignals(0);
10959  // this is ok as here we are setting signals from the start of the route
10960  }
10961  }
10962  SignalsSet = true;
10963  }
10964  }
10965  if(NewTrain->MidElement > -1)
10966  // if entering at a continuation MidElement == -1
10967  {
10968  // this is included in case a train starts with LeadElement on no route and MidElement on a route
10969  if(!SignalsSet)
10970  {
10971  RouteNumber = -1;
10972  if(AllRoutes->GetRouteTypeAndNumber(14, NewTrain->MidElement, NewTrain->MidEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10973  {
10974  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10975  int RouteStartPosition;
10976  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10977  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(22, Track->TrackElementAt(957, RearPosition).HLoc,
10978  Track->TrackElementAt(958, RearPosition).VLoc, SecondPair);
10979  if(FirstPair.first == RouteNumber)
10980  {
10981  RouteStartPosition = FirstPair.second;
10982  }
10983  else if(SecondPair.first == RouteNumber)
10984  {
10985  RouteStartPosition = SecondPair.second;
10986  }
10987  else
10988  {
10989  throw Exception("Error, RouteNumber not found in Route2MultiMap in 2nd of 2 calls to SetAllRearwardsSignals in AddTrain");
10990  }
10991  AllRoutes->SetAllRearwardsSignals(11, 0, RouteNumber, RouteStartPosition);
10992  SignalsSet = true;
10993  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10994  }
10995  else if(RouteNumber > -1) // non-autosigsroute
10996  {
10997  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(184, RouteNumber).GetFixedPrefDirElementAt(196, 0);
10998  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10999  int FirstELinkPos = TempPDE.GetELinkPos();
11000  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->MidElement))
11001  {
11002  AllRoutes->RemoveRouteElement(18, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
11003  TempPDE = AllRoutes->GetFixedRouteAt(185, RouteNumber).GetFixedPrefDirElementAt(197, 0);
11004  }
11005  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->MidElement))
11006  {
11007  AllRoutes->RemoveRouteElement(19, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
11008  // remove the last element under LeadElement
11009  }
11010  AllRoutes->RebuildRailwayFlag = true;
11011  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
11012  // now deal with a rear linked autosigs route
11013  if(Track->TrackElementAt(823, FirstTVPos).Conn[FirstELinkPos] > -1)
11014  {
11015  int LinkedRouteNumber = -1;
11016  if(AllRoutes->GetRouteTypeAndNumber(19, Track->TrackElementAt(824, FirstTVPos).Conn[FirstELinkPos],
11017  Track->TrackElementAt(825, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
11018  {
11019  AllRoutes->GetFixedRouteAt(170, LinkedRouteNumber).SetRouteSignals(1);
11020  // this is ok as now we are setting signals from the start of the route
11021  }
11022  }
11023  }
11024  }
11025  }
11026  TrainVector.push_back(*NewTrain);
11027  Utilities->CallLogPop(731);
11028  return(true);
11029 }
11030 
11031 // ---------------------------------------------------------------------------
11032 
11033 int TTrainController::EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
11034 {
11035  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EntryPos," + AnsiString(TrainIDIn) + "," +
11036  AnsiString(TrackVectorNumber));
11037  int VecPos = -1;
11038 
11039  for(unsigned int x = 0; x < TrainVector.size(); x++)
11040  {
11041  if(TrainVectorAt(1, x).TrainID == TrainIDIn)
11042  {
11043  VecPos = x;
11044  }
11045  }
11046  if(VecPos == -1)
11047  {
11048  throw Exception("Error, VecPos not set in EntryPos");
11049  }
11050  if(TrainVectorAt(2, VecPos).LeadElement == TrackVectorNumber)
11051  {
11052  Utilities->CallLogPop(734);
11053  return(TrainVectorAt(3, VecPos).LeadEntryPos);
11054  }
11055  else if(TrainVectorAt(4, VecPos).MidElement == TrackVectorNumber)
11056  {
11057  Utilities->CallLogPop(735);
11058  return(TrainVectorAt(5, VecPos).MidEntryPos);
11059  }
11060  else if(TrainVectorAt(6, VecPos).LagElement == TrackVectorNumber)
11061  {
11062  Utilities->CallLogPop(736);
11063  return(TrainVectorAt(7, VecPos).LagEntryPos);
11064  }
11065  Utilities->CallLogPop(737);
11066  return(-1);
11067 }
11068 
11069 // ---------------------------------------------------------------------------
11070 
11072 {
11073  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAtIdent," + AnsiString(TrainID));
11074  for(unsigned int x = 0; x < TrainVector.size(); x++)
11075  {
11076  if(TrainVectorAt(53, x).TrainID == TrainID)
11077  {
11078  Utilities->CallLogPop(738);
11079  return(TrainVectorAt(54, x));
11080  }
11081  }
11082  throw Exception("Error - No Train identified in TrainVectorAtIdent with ID = " + AnsiString(TrainID));
11083 }
11084 
11085 // ---------------------------------------------------------------------------
11086 
11087 bool TTrainController::TrainExistsAtIdent(int Caller, int TrainID)
11088 // return true if find the train (added at v2.4.0 as can select a removed train in
11089 // ActionsDueListBox before it updates - reported by LiWinDom in error report 23/04/20)
11090 {
11091  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainExistsAtIdent," + AnsiString(TrainID));
11092  for(unsigned int x = 0; x < TrainVector.size(); x++)
11093  {
11094  if(TrainVectorAt(69, x).TrainID == TrainID)
11095  {
11096  Utilities->CallLogPop(2152);
11097  return(true);
11098  }
11099  }
11100  Utilities->CallLogPop(2153);
11101  return(false);
11102 }
11103 
11104 // ---------------------------------------------------------------------------
11105 
11106 TDateTime TTrainController::GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
11107 {
11108  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetControllerTrainTime," + AnsiString(RepeatNumber) + "," +
11109  Utilities->Format96HHMMSS(Time));
11110  TDateTime RepeatTime = TrainController->GetRepeatTime(47, Time, RepeatNumber, IncrementalMinutes);
11111 
11112  Utilities->CallLogPop(2061);
11113  return(RepeatTime);
11114 }
11115 
11116 // ---------------------------------------------------------------------------
11117 
11118 AnsiString TTrainController::ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepNum, int IncMins, int IncDig)
11119 // Enter with Ptr pointing to first action to be listed (i.e. next action)
11120 {
11121  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationEntryFloatingTTString" + "," + TTDEPtr->HeadCode);
11122  AnsiString RetStr = "", PartStr = "", MinMinsString;
11123  int Count = 0;
11124  TActionVectorIterator Ptr = TTDEPtr->ActionVector.begin();
11125 
11126  Ptr--; // because incremented at start of loop
11127  do
11128  {
11129  Ptr++;
11130  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
11131  {
11132  continue; // move past the starting entry
11133  }
11134  if((Ptr->FormatType == Repeat) || Ptr >= TTDEPtr->ActionVector.end())
11135  {
11136  break;
11137  }
11138  if(Ptr->SignallerControl)
11139  {
11140  RetStr = "Train under signaller control";
11141  break;
11142  }
11143  if(Ptr->FormatType == TimeTimeLoc)
11144  {
11145  if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
11146  {
11147  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11148  {
11149  double MDTdouble = Ptr->MinDwellTime / 60;
11150  double MDT = int(MDTdouble * 10);
11151  MDT = MDT / 10;
11152  MinMinsString = "mins";
11153  if((MDT < 1.1) && (MDT > 0.9))
11154  {
11155  MinMinsString = "min";
11156  }
11157  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(34, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
11158  }
11159  else
11160  {
11161  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(86, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName;
11162  }
11163  }
11164  else
11165  {
11166  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11167  {
11168  double MDTdouble = Ptr->MinDwellTime / 60;
11169  double MDT = int(MDTdouble * 10);
11170  MDT = MDT / 10;
11171  MinMinsString = "mins";
11172  if((MDT < 1.1) && (MDT > 0.9))
11173  {
11174  MinMinsString = "min";
11175  }
11176  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(16, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
11177  Utilities->Format96HHMM(GetControllerTrainTime(85, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11178  }
11179  else
11180  {
11181  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(80, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + '\n' +
11182  Utilities->Format96HHMM(GetControllerTrainTime(84, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11183  }
11184  Count++; // because there are 2 entries
11185  }
11186  }
11187  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1))) //arrival
11188  {
11189  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11190  {
11191  double MDTdouble = Ptr->MinDwellTime / 60;
11192  double MDT = int(MDTdouble * 10);
11193  MDT = MDT / 10;
11194  MinMinsString = "mins";
11195  if((MDT < 1.1) && (MDT > 0.9))
11196  {
11197  MinMinsString = "min";
11198  }
11199  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(87, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
11200  }
11201  else
11202  {
11203  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(35, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName;
11204  }
11205  }
11206  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1))) //departure
11207  {
11208  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(4, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11209  }
11210  else if(Ptr->FormatType == PassTime) // new
11211  {
11212  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(5, Ptr->EventTime, RepNum, IncMins)) + ": Pass " + Ptr->LocationName;
11213  }
11214  else if(Ptr->Command == "Fns")
11215  {
11216  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(6, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11217  TrainController->GetRepeatHeadCode(46, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11218  PartStr = ControllerGetNewServiceDepartureInfo(11, Ptr, RepNum, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to PartStr
11219  }
11220  else if(Ptr->Command == "F-nshs")
11221  {
11222  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(7, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11223  Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;
11224  PartStr = ControllerGetNewServiceDepartureInfo(13, Ptr, 0, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11225  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
11226  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
11227  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
11228  }
11229 //Since this is a new continuation entry service it can't be Fns-sh or Frh-sh but leave these in for consistency with TTrain::FloatingTimetableString
11230  else if((Ptr->Command == "Fns-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11231  {
11232  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(8, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11233  TrainController->GetRepeatHeadCode(47, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11234  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11235  PartStr = ControllerGetNewServiceDepartureInfo(15, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11236  }
11237  else if((Ptr->Command == "Fns-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11238  {
11239  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(9, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11240  Ptr->NonRepeatingShuttleLinkHeadCode, +" at " + Ptr->LocationName;
11241  PartStr = ControllerGetNewServiceDepartureInfo(17, Ptr, 0, TTDEPtr, Ptr->NonRepeatingShuttleLinkEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11242  }
11243  else if((Ptr->Command == "Frh-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11244  {
11245  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(10, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11246  TrainController->GetRepeatHeadCode(48, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11247  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11248  PartStr = ControllerGetNewServiceDepartureInfo(19, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11249  }
11250  else if((Ptr->Command == "Frh-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11251  {
11252  PartStr = "Terminate at " + Ptr->LocationName;
11253  }
11254  else if(Ptr->Command == "Frh")
11255  {
11256  PartStr = "Terminate at " + Ptr->LocationName;
11257  }
11258  else if(Ptr->Command == "Fer")
11259  {
11260  AnsiString AllowedExits;
11261  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(11, Ptr->EventTime, RepNum, IncMins)) + ": Exit railway" +
11262  TrainController->GetExitLocationAndAt(3, Ptr->ExitList, AllowedExits) + AllowedExits;
11263  }
11264  else if(Ptr->Command == "Fjo")
11265  {
11266  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(12, Ptr->EventTime, RepNum, IncMins)) + ": Join " + TrainController->GetRepeatHeadCode(49,
11267  Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11268  }
11269  else if(Ptr->Command == "jbo")
11270  {
11271  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(13, Ptr->EventTime, RepNum, IncMins)) + ": Joined by " + TrainController->GetRepeatHeadCode
11272  (50, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11273  }
11274  else if(Ptr->Command == "fsp")
11275  {
11276  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(14, Ptr->EventTime, RepNum, IncMins)) + ": Front split to " +
11277  TrainController->GetRepeatHeadCode(51, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11278  }
11279  else if(Ptr->Command == "rsp")
11280  {
11281  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(15, Ptr->EventTime, RepNum, IncMins)) + ": Rear split to " +
11282  TrainController->GetRepeatHeadCode(52, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11283  }
11284  else if(Ptr->Command == "cdt")
11285  {
11286  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(88, Ptr->EventTime, RepNum, IncMins)) + ": Change direction at " + Ptr->LocationName;
11287  }
11288  else if(Ptr->Command == "dsc")
11289  {
11290  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(27, Ptr->EventTime, RepNum, IncMins)) + ": Change description at " + Ptr->LocationName;
11291  }
11292  else if(Ptr->Command == "cms")
11293  {
11294  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(32, Ptr->EventTime, RepNum, IncMins)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
11295  }
11296  if(RetStr != "")
11297  {
11298  RetStr = RetStr + '\n' + PartStr;
11299  }
11300  else
11301  {
11302  RetStr = PartStr;
11303  }
11304  Count++;
11305  }
11306  while(Ptr < TTDEPtr->ActionVector.end() && (Count < 33) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
11307  // limit of 33 allows a max of 34 entries (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and train status gives
11308  // a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
11309  // forward as anyone should wish to see without looking at the full timetable
11310  Utilities->CallLogPop(2072);
11311  return(RetStr);
11312 }
11313 
11314 // ---------------------------------------------------------------------------
11315 
11317  TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
11318 { //no delays as train not entered yet //above added IncrementalDigits at v2.18.0 for GetRepeatHeadCode
11319  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - TDEPtr->ActionVector.begin()) + ","
11320  + AnsiString(RptNum) + ",ControllerGetNewServiceDepartureInfo," + TDEPtr->HeadCode);
11321  AnsiString DepTime = "", EventTime = "";
11322  bool CDTFlag = false; //reports if train changes direction before departs
11323  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
11324  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
11325  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
11326  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
11327  {
11328  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
11329  {
11330  TowardsLocation = AVI->LocationName;
11331  }
11332  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
11333  {
11334  TTrackElement TE = Track->TrackElementAt(1453, (AVI->ExitList.front()));
11335  if(TE.ActiveTrackElementName != "")
11336  {
11337  TowardsLocation = TE.ActiveTrackElementName;
11338  }
11339  else
11340  {
11341  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
11342  }
11343  }
11344  }
11345  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
11346  {
11347  if(AVI->Command == "cdt")
11348  {
11349  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
11350  continue;
11351  }
11352  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
11353  {
11354  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(21, AVI->EventTime, RptNum, IncrementalMinutes));
11355  RetStr += "\nNew service splits at " + EventTime;
11356  Utilities->CallLogPop(2237);
11357  return(RetStr);
11358  }
11359  if(AVI->Command == "jbo")
11360  {
11361  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(30, AVI->EventTime, RptNum, IncrementalMinutes));
11362  RetStr += "\nNew service joined by " + GetRepeatHeadCode(70, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11363  Utilities->CallLogPop(2238); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11364  return(RetStr);
11365  }
11366  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh"))
11367  {
11368  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(31, AVI->EventTime, RptNum, IncrementalMinutes));
11369  RetStr += "\nNew service finishes and forms another new service at " + EventTime;
11370  Utilities->CallLogPop(2607);
11371  return(RetStr);
11372  }
11373  if(AVI->Command == "Fjo")
11374  {
11375  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(22, AVI->EventTime, RptNum, IncrementalMinutes));
11376  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(71, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11377  Utilities->CallLogPop(2608); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11378  return(RetStr);
11379  }
11380  if(AVI->Command == "Frh")
11381  {
11382  RetStr += "\nNew service finishes and remains at location.";
11383  Utilities->CallLogPop(2609);
11384  return(RetStr);
11385  }
11386  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
11387  {
11388  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(23, AVI->DepartureTime, RptNum, IncrementalMinutes));
11389  if(CDTFlag)
11390  {
11391  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11392  {
11393  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
11394  }
11395  else
11396  {
11397  RetStr += "\nNew service changes direction then departs at " + DepTime;
11398  }
11399  }
11400  else
11401  {
11402  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11403  {
11404  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
11405  }
11406  else
11407  {
11408  RetStr += "\nNew service departs at " + DepTime;
11409  }
11410  }
11411  Utilities->CallLogPop(2239);
11412  return(RetStr);
11413  }
11414  }
11415  Utilities->CallLogPop(2223);
11416  return(RetStr);
11417 }
11418 
11419 // ---------------------------------------------------------------------------
11420 // $$$$$$$$$$$$$$$$$$$$$$ Start of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$$
11421 /*
11422  Note: The terms 'action' and 'entry' have been used freely for individual code lines within services in comments & in variable names, but
11423  for messages and in the manual and help files the term Entry is reserved for a complete service or train (i.e. an entry in the timetable),
11424  and 'event' is reserved for and individual code line within a service. Repeats use the term 'item' if they use any at all.
11425 
11426  In references to 'HeadCode' can have an optional prefix - up to 4 additional characters that can be anything, so long as last 4 digits
11427  represent the headcode. This allows links to be uniquely identified regardless of the headcode - so can have same headcodes as often as
11428  user wishes
11429 
11430  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11431  descriptive text or anything user wishes
11432  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11433  be ignored) is taken as the timetable start time.
11434  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11435  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11436  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11437  within the timetable if required.
11438  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11439  services)
11440  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11441  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11442 
11443  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11444  text timetable file easier
11445 
11446  form:-
11447  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11448  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11449  then multiple entries, separated by commas, of the form:-
11450 
11451  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11452  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
11453  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
11454 
11455  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
11456  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
11457  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
11458 
11459  HH:MM;Command (cdt) }TimeCmd }
11460  HH:MM;Command;Description (dsc) }TimeCmdDescription }
11461  HH:MM;Command;NewMaxSpeed (cms) }TimeCmdMaxSpeed }
11462  HH:MM;Location (arr & dep) }TimeLoc }
11463  HH:MM;HH:MM;Location }TimeTimeLoc }
11464  HH:MM;pas;Location }PassTime }
11465  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
11466  HH:MM;Fer;set of allowable IDs }ExitRailway }
11467  Command (Frh only) }FinRemHere }
11468 
11469  R;mm;dd;nn. Repeat Repeat entry
11470 
11471  Formats:
11472 
11473  Command only: Frh
11474  Time;Command: cdt
11475  Time;Command;Description dsc
11476  Time;Command;MaxSpeed msc
11477  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
11478  Time;Command;2 Element IDs: Snt
11479  Time;Comand;n Element IDs: Fer
11480  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
11481  Time;Command;2 Element IDs;Headcode Snt-sh
11482  Time;Command;Location pas
11483  Time;Location Arr Dep
11484  Time;Time;Location Arr & dep together
11485 
11486  11 Non-linked entries: Snt (located & unlocated); pas; cdt; dsc; msc; TimeLoc arr & dep; TimeTimeLoc; Fer; Frh
11487 
11488  9 1x Linked entries: Non-shuttle: fsp or rsp -> Sfs; Fns -> Sns; Fjo -> jbo; times must match, headcodes must match
11489  Shuttle: F-nshs -> Sns-sh: times match, F-nshs HeadCode matches Sns-sh 2nd Headcode;
11490  Fns-sh -> Sns-fsh: Fns-sh time + all repeats = Sns-fsh time, Fns-sh 2nd headcode matches Sns-fsh Headcode
11491 
11492  4 2x Linked entries, all shuttles:
11493 
11494  Frh-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Frh-sh Headcode = Snt-sh Headcode;
11495  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Frh-sh Headcode = Sns-sh 1st Headcode;
11496  -> Remain Here (at finish location after all repeats)
11497  Fns-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Snt-sh Headcode
11498  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Sns-sh 1st Headcode
11499 
11500  Moving/AtLoc states:-
11501 
11502  Successor state Type
11503 
11504  Snt located AtLoc ) Snt AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/msc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
11505  Snt Unlocated Moving ) Snt Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
11506  Sfs AtLoc )
11507  Sns AtLoc ) Start
11508  Sns-fsh AtLoc )
11509  Snt-sh AtLoc )
11510  Sns-sh AtLoc )
11511 
11512  pas Moving )
11513  jbo AtLoc )
11514  fsp AtLoc )
11515  rsp AtLoc ) Intermediate
11516  cdt AtLoc )
11517  dsc AtLoc )
11518  msc AtLoc )
11519  TimeLoc arr Moving (bef), AtLoc (aft) )
11520  TimeLoc dep AtLoc (bef), Moving (aft) )
11521  TimeTimeLoc Moving )
11522 
11523  Fns Repeat/Nothing)
11524  Fjo Repeat/Nothing)
11525  Frh Repeat/Nothing)
11526  Fer Repeat/Nothing) Finish
11527  Frh-sh Repeat )
11528  Fns-sh Repeat )
11529  F-nshs Nothing )
11530 
11531  Descriptions:
11532  Snt New train
11533  Sfs New service from split
11534  Sns New service from another service
11535  Sns-fsh New non-repeating service from a shuttle service
11536  Snt-sh New shuttle train at a timetabled stop
11537  Sns-sh New shuttle service from a feeder service
11538 
11539  pas Pass
11540  jbo Be joined by another train
11541  fsp Front split
11542  rsp Rear split
11543  cdt Change direction of train
11544  dsc Change description of train
11545  msc Change max speed of train
11546  TimeLoc arr Arrival
11547  TimeLoc dep Departure
11548  TimeTimeLoc Arrival and departure
11549 
11550  Fns Finish & form a new service
11551  Fjo Finish & join another train
11552  Frh Finish & remain here
11553  Fer Finish & exit railway
11554  Frh-sh Finish & repeat shuttle, finally remain here
11555  Fns-sh Finish & repeat shuttle, finally form a non-repeating service
11556  F-nshs Finish & form a shuttle feeder service
11557 */
11558 
11559 bool TTrainController::TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway) // true for success
11560 {
11561  // Error messages mainly given in called functions, five are given here - empty file; inability to find a start time; timetable containing
11562  // a line that is too long; timetable containing too few lines; and timetable failed to open.
11563  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TimetableIntegrityCheck," + AnsiString(FileName));
11564  // new for v0.2b
11565  // compile ActiveTrackElementNameMap
11566  TTrack::TActiveTrackElementNameMapEntry ActiveTrackElementNameMapEntry;
11567 
11569  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
11570  {
11571  // if((Track->TrackElementAt(, x).ActiveTrackElementName != "") && (Track->TrackElementAt(, x).TrackType != Continuation))
11573  == Track->ContinuationNameMap.end())
11574  {
11575  // exclude any name that appears in a continuation, error message given in tt validation if try to include such a name in a tt
11576  ActiveTrackElementNameMapEntry.first = Track->TrackElementAt(1035, x).ActiveTrackElementName;
11577  ActiveTrackElementNameMapEntry.second = 0; // this is a dummy value
11578  Track->ActiveTrackElementNameMap.insert(ActiveTrackElementNameMapEntry);
11579  }
11580  }
11582  // end of new section
11583  std::ifstream TTBLFile(FileName, std::ios_base::binary);
11584 
11585  // binary mode so the "\r\n" pairs stay as they are rather than being entered as '\n'
11586  if(TTBLFile.is_open())
11587  {
11588  char *TrainTimetableString = new char[10000];
11589  // enough for over 200 stations, should be adequate!
11590  bool EndOfFile = false;
11591  int Count = 0;
11592  // counts 'relevant' lines, i.e ignores any before the start time on its own line
11593  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11594  // delimiter is '\0' as it's an AnsiString
11595  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11596  // file empty - stores a null in 1st position if doesn't load any characters
11597  {
11598  // may still have eof even if read a line (no CRLF at end), and
11599  // if so need to process it
11600  TimetableMessage(GiveMessages, "Timetable invalid - file empty");
11601  TTBLFile.close();
11602  delete[] TrainTimetableString;
11603  Utilities->CallLogPop(1611);
11604  return(false);
11605  }
11606  AnsiString OneEntry(TrainTimetableString);
11607  bool FinalCallFalse = false;
11608  while((Count == 0) && !ProcessOneTimetableEntry(5, Count, OneEntry, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11609  // get rid of lines before the start time
11610  {
11611  // ProcessOneTimetableLine returns true for a valid start time, an EndOfFile &/or a blank entry
11612  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11613  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11614  // stores a null in 1st position if doesn't load any characters
11615  {
11616  // may still have eof even if read a line (no CRLF at end), and
11617  // if so need to process it
11618  TimetableMessage(GiveMessages, "Timetable invalid - unable to find a valid start time on its own line");
11619  TTBLFile.close();
11620  delete[] TrainTimetableString;
11621  Utilities->CallLogPop(772);
11622  return(false);
11623  }
11624  OneEntry = AnsiString(TrainTimetableString);
11625  }
11626  // here when have accepted the start time
11627  Count++; // increment past the start time
11628  while(!EndOfFile)
11629  {
11630  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11631  // get next line after start time
11632  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11633  // stores a null in 1st position if doesn't load any characters
11634  {
11635  // may still have eof even if read a line (no CRLF at end), and
11636  // if so need to process it
11637  EndOfFile = true;
11638  OneEntry = "";
11639  }
11640  else
11641  {
11642  OneEntry = AnsiString(TrainTimetableString);
11643  }
11644  if(OneEntry.Length() > 9999)
11645  {
11646  TimetableMessage(GiveMessages, "Timetable contains a line that is too long - 10,000 or more characters!");
11647  TTBLFile.close();
11648  delete[] TrainTimetableString;
11649  Utilities->CallLogPop(789);
11650  return(false);
11651  }
11652  bool FinalCallFalse = false;
11653  if(!ProcessOneTimetableEntry(6, Count, OneEntry, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11654  // false for FinalCall - just checking at this stage
11655  {
11656  TTBLFile.close();
11657  delete[] TrainTimetableString;
11658  Utilities->CallLogPop(770);
11659  return(false);
11660  }
11661  if(EndOfFile && (Count < 2))
11662  // Timetable must contain at least two relevant lines, one for start time and at least one train
11663  {
11664  TimetableMessage(GiveMessages, "Timetable has too few or no relevant entries - must have a start time on its own line and at least one train");
11665  TTBLFile.close();
11666  delete[] TrainTimetableString;
11667  Utilities->CallLogPop(771);
11668  return(false);
11669  }
11670  Count++;
11671  }
11672  delete[] TrainTimetableString;
11673  TTBLFile.close();
11674  } // if(TTBLFile.is_open())
11675  else
11676  {
11677  TimetableMessage(GiveMessages, "Failed to open timetable file, make sure it's spelled correctly, it exists and isn't open in another application");
11678  Utilities->CallLogPop(2154);
11679  return(false);
11680  }
11681  Utilities->CallLogPop(753);
11682  return(true);
11683 }
11684 
11685 // ---------------------------------------------------------------------------
11686 
11687 bool TTrainController::ProcessOneTimetableEntry(int Caller, int Count, AnsiString OneEntry, bool &EndOfFile, bool FinalCall, bool GiveMessages,
11688  bool CheckLocationsExistInRailway) // return true for success
11689  //changed name to ProcessOneTimetableEntry from ...Line & AnsiString OneEntry from ...OneLine
11690 /* Format: // to avoid confusion with single lines of entries
11691  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11692  descriptive text or anything user wishes
11693  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11694  be ignored) is taken as the timetable start time.
11695  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11696  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11697  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11698  within the timetable if required.
11699  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11700  services)
11701  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11702  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11703 
11704  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11705  text timetable file easier
11706 
11707  form:-
11708  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11709  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11710  then multiple entries, separated by commas, of the form:-
11711 
11712  Format FormatType
11713  [W]HH:MM;Command (cdt) }TimeCmd }
11714  [W]HH:MM;dsc;new description }TimeCmdDescription }
11715  [W]HH:MM;cms;new max speed }TimeCmdMaxSpeed }
11716  [W]HH:MM;Fer;set of allowable IDs }ExitRailway }
11717  [W]HH:MM;pas;Location }PassTime }
11718  [W]HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11719  [W]HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode }
11720  [W]HH:MM;F-nshs;non-repeating headcode }FNSNonRepeatToShuttle }
11721  [W]HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle } Train action entries
11722  [W]HH:MM;Snt-sh;RearStartIdent FrontStartIdent;FSH HeadCode }SNTShuttle }
11723  [W]HH:MM;Sns-sh;FSH HeadCode;F-nshs HeadCode (non-repeating) }SNSShuttle }
11724  [W]HH:MM;Fns-sh;Details }FSHNewService }
11725  [W]HH:MM;Location (arr & dep) }TimeLoc }
11726  [W]HH:MM;HH:MM;Location }TimeTimeLoc }
11727  Command (Frh only) }FinRemHere }
11728 
11729  R;mm;dd;nn. Repeat Repeat entry
11730 
11731  Two times represent arrival & departure, without any other events between (if arrival and departure times are the same
11732  then departure is 30 sec after arrival), single time represents (a) event time; (b) arrival time if train not already
11733  at location; or (c) departure time if train already at location (including train started at location either as a new
11734  train or as a continuation service train at that location). All lines must contain a start entry and a finish entry,
11735  the finish being the last unless there is a repeat entry. The repeat entry begins with 'R', then the incremental
11736  minutes, incremental train headcode last 2 digits, and number of repeats.
11737 
11738  Shuttle entries are where can loop back to an earlier Snt-sh or Sns-sh entry from a Frh-sh or Fns-sh (Finish Shuttle)
11739  entry. Here the shuttle start can have two entries, one from a set position (Snt-sh, must be located) or from a F-nshs
11740  (Sns-sh) - with NO repeat from this source, and from a Fxx-sh, with repeats. After all shuttle repeats Frh-sh remains
11741  where it is, and Fns-sh links to a new service (via an Sns entry), but there must be no repeats in this new service
11742  (it's for a shuttle train to return to depot at end of services)
11743 
11744  Command/Location & details are as follows:-
11745 
11746  Although headcodes can be duplicated, all joins, splits, new services etc give other headcode from both trains' povs, and
11747  these have to match once only, i.e. if 2E44 splits to 2E45 then it can't split to 2E45 anywhere else, and 2E45 must give
11748  2E44 in its Sfs entry. All these are checked.
11749  ***add note re shuttles & their use of otherheadcodes + non-repeating headcodes***
11750 
11751  Start commands:-
11752  Snt (StartNew) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't confuse
11753  with loc as a start entry can't have a location as details)
11754  Sfs (TimeCmdHeadCode) = Start From Split, create a new train that has split from another train (& listed in other train's
11755  timetable line), details = other headcode - (can't confuse with loc as start can't be a loc)
11756  Sns (TimeCmdHeadCode) = Start, headcode change from earlier service - no need to create train as already exists, it just
11757  changes its relevant information, details = old headcode (can't confuse with loc as start can't be a loc)
11758  Snt-sh (SNTShuttle) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't
11759  confuse with loc as start can't be a loc) then the Fsh-XX service headcode (OtherHeadCode can't be same headcode)
11760  Sns-sh (SNSShuttle) = Start, headcode change from earlier service - no need to create train as already exists, it just
11761  changes its relevant information, details = the FSH-XX service headcode (OtherHeadCode, can't be same headcode)
11762  followed by the non-repeating F-nshs headcode (NonRepeatingShuttleLinkHeadCode)
11763  Sns-fsh (SNSNonRepeatFromShuttle) = Start as a non-repeating service from a shuttle service that has finished all its
11764  repeats, details = NonRepeatingShuttleLinkHeadCode for the corresponding shuttle Fns-sh service
11765 
11766  Intermediate commands:-
11767  Time - Location (TimeLoc), can be arrival or departure depending on context
11768  Time Time location (TimeTimeLoc), arrival and departure
11769  Location Name (exactly as used in the railway) in TimeLoc & TimeTimeLoc means that the train is required to stop at the location
11770  pas (PassTime), Time;pas;Location
11771  jbo (TimeCmdHeadCode) = Joined By Other = joined by other train, details = new headcode (await other train - may be delayed). Note that the
11772  joining train's finish details must correspond or the file check will fail
11773  fsp (TimeCmdHeadCode) = Front Split = a new train splits away from front of this train, both trains in same direction, details = new headcode (create
11774  new train - that train's starting information must correspond)
11775  rsp (TimeCmdHeadCode) = Rear Split = a new train splits away from rear of this train, both trains in same direction, details = new headcode (create
11776  new train - that train's starting information must correspond)
11777  cdt (TimeCmd) = Change Direction of Train = change direction, no details needed & no train creation
11778  dsc (TimeCmdDescription) = Change description of train = new description only, no train creation
11779  cms (TimeCmdMaxSpeed) = Change maximum speed of train = new max speed only, no train creation
11780 
11781  Finish commands:-
11782  Fns (TimeCmdHeadCode) = Finish New Service = finish, form new service in same direction, details = new headcode (no train
11783  creation)
11784  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
11785  shuttle headcode (no train creation)
11786  Fjo (TimeCmdHeadCode) = Finish Join Other = finish, join other train (which must be on an adjacent element, either end -
11787  may have to wait for it), details = new headcode (delete train)
11788  Frh (FinRemHere) = Finish Remain Here = stay here indefinitely, no details & no time needed
11789  Fer (ExitRailway) = Finish, exit railway (i.e at a continuation) - details = set of allowable exit IDs
11790  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done remain
11791  here
11792  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done form new
11793  service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
11794 
11795  Repeat:-
11796  R;mm;dd;nn (Repeat) where mm = minute increment, dd = 2nd 2 headcode digit increment & nn = no. of repeats (no max as can duplicate
11797  headcodes - it is up to user to avoid duplicates if he/she wishes to.
11798 
11799  Checks carried out with error messages in this function:-
11800  At least one comma in a service line (it's based on a .csv file)
11801  No entries following train information;
11802  At least one comma in remainder after train information (i.e at least a start and a finish entry);
11803  SplitEntry returns false in an intermediate entry - message repeats the entry for information;
11804  First entry not a start entry;
11805  Train information incomplete before a start entry;
11806  Entry follows a finish entry but doesn't begin with 'R';
11807  SplitEntry returns false in a finish entry - message repeats the entry for information;
11808  Last action entry isn't a finish entry.
11809 
11810  Function returns false with no message if:-
11811  Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
11812  time is found at all then an error message is given in the calling function);
11813  SplitTrainInfo returns false (message given in called function);
11814  SplitRepeat returns false (message given in called function).
11815 */
11816 {
11817  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ProcessOneTimetableEntry," + AnsiString(Count) + "," + OneEntry + "," +
11818  AnsiString((short)FinalCall) + "," + AnsiString((short)CheckLocationsExistInRailway));
11819  TTrainDataEntry TempTrainDataEntry;
11820 
11821  EndOfFile = false;
11822  StripSpaces(0, OneEntry);
11823  // strip both leading and trailing spaces at ends of line and spaces before and after all commas and
11824  // semicolons within the line
11825  ServiceReference = "";
11826  if(OneEntry != "")
11827  {
11828  if(OneEntry[1] != '*')
11829  {
11830  int DelimPos = OneEntry.Pos(';');
11831  int CPos = OneEntry.Pos(','); //added at v2.21.0 as Sns entries without a description have comma instead of semicolon but service ref. still valid
11832  if((CPos > 0) && (CPos < DelimPos))
11833  {
11834  DelimPos = CPos;
11835  }
11836 
11837  if(DelimPos == 0)
11838  {
11839  ServiceReference = OneEntry.SubString(1, 8);
11840  }
11841  else
11842  {
11843  ServiceReference = OneEntry.SubString(1, (DelimPos - 1));
11844  }
11845  }
11846  }
11847  bool AllCommas = true;
11848 
11849  for(int x = 1; x < OneEntry.Length() + 1; x++) // check for nothing but commas (may be all commas if created from Excel) or a blank line
11850  {
11851  if(OneEntry[x] != ',')
11852  {
11853  AllCommas = false;
11854  }
11855  }
11856  if(AllCommas || (OneEntry == ""))
11857  {
11858  if(Count > 0)
11859  {
11860  EndOfFile = true;
11861  // returns true for a blank line (or a line of all commas) - treated as end of file
11862  Utilities->CallLogPop(1018);
11863  return(true);
11864  }
11865  else // count == 0 so not yet found a start time, no message to be given
11866  {
11867  Utilities->CallLogPop(754);
11868  return(false);
11869  }
11870  }
11871  AnsiString First = "", Second = "", Third = "", Fourth = "";
11872  int RearStartOrRepeatMins = 0, FrontStartOrRepeatDigits = 0, NumberOfRepeats = 0;
11873  TDateTime EventTime(0), ArrivalTime(0), DepartureTime(0);
11874  TDateTime StartTime(0);
11875  TNumList ExitList;
11876  bool Warning = false;
11877 
11878  if(Count == 0) // no start time found yet
11879  {
11880 /* dropped at v0.6b
11881  AnyHeadCodeValid = false;
11882  if(OneLine.SubString(6,5) == ";0000")
11883  {
11884  AnyHeadCodeValid = true;
11885  }
11886 */
11887  if(!CheckTimeValidity(0, OneEntry, StartTime))
11888  {
11889  // no message is given for an invalid time as it's assumed to be an irrelevant line
11890  // if no start time is found at all then an error message is given in the calling function
11891  // AnyHeadCodeValid = false;
11892  Utilities->CallLogPop(755);
11893  return(false);
11894  }
11895  if(FinalCall) // here if start time valid
11896  {
11897  TTClockTime = StartTime;
11898  TimetableStartTime = StartTime;
11899  }
11900  }
11901  else
11902  {
11903  AnsiString TrainInfoStr = "", HeadCode = "", Description = "";
11904  int StartSpeed = 0, MaxRunningSpeed = 0, Mass = 0;
11905  double MaxBrakeRate = 0;
11906  double PowerAtRail = 0;
11907  int SignallerSpeed = 0;
11908  if(OneEntry[1] == '*')
11909  {
11910  Utilities->CallLogPop(1581);
11911  return(true);
11912  // ignore any line beginning with '*' but return true as there is no error
11913  }
11914  int Pos = OneEntry.Pos(',');
11915  if(Pos == 0)
11916  {
11917  int SubStringLength = 20;
11918  if(OneEntry.Length() < 20)
11919  {
11920  SubStringLength = OneEntry.Length();
11921  }
11922  TimetableMessage(GiveMessages, "Error in timetable - entry incomplete: see '" + OneEntry.SubString(1, SubStringLength) + "'....");
11923  Utilities->CallLogPop(766);
11924  return(false);
11925  }
11926  TrainInfoStr = OneEntry.SubString(1, Pos - 1);
11927  if(!SplitTrainInfo(0, TrainInfoStr, HeadCode, Description, StartSpeed, MaxRunningSpeed, Mass, MaxBrakeRate, PowerAtRail, SignallerSpeed,
11928  GiveMessages)) // error messages given in SplitTrainInfo
11929  {
11930  Utilities->CallLogPop(773);
11931  return(false);
11932  }
11933  if(FinalCall)
11934  {
11935  // store Train info - conversions done in SplitTrainInfo
11936  // only headcode mandatory for continuing services
11937  //HeadCode = ServiceReference until final section of SecondPassActions
11938  TempTrainDataEntry.HeadCode = HeadCode;
11939  TempTrainDataEntry.ServiceReference = HeadCode;
11940  TempTrainDataEntry.FixedDescription = Description; //name changed at v2.16.1
11941  TempTrainDataEntry.ExplicitDescription = false;
11942  if(Description != "")
11943  {
11944  TempTrainDataEntry.ExplicitDescription = true;
11945  }
11946  TempTrainDataEntry.StartSpeed = StartSpeed;
11947  TempTrainDataEntry.Mass = Mass;
11948  TempTrainDataEntry.MaxRunningSpeed = MaxRunningSpeed;
11949  TempTrainDataEntry.MaxBrakeRate = MaxBrakeRate;
11950  TempTrainDataEntry.PowerAtRail = PowerAtRail;
11951  TempTrainDataEntry.SignallerSpeed = SignallerSpeed;
11952  TTrainOperatingData TempTrainOperatingData;
11953  TempTrainDataEntry.TrainOperatingDataVector.push_back(TempTrainOperatingData); // push empty vector for now
11954  }
11955  AnsiString NewRemainder = OneEntry.SubString(Pos + 1, OneEntry.Length() - Pos);
11956  // now left with series of entries for this train, but there may be a string of commas at the end of the line if created by Excel
11957  // so strip them off
11958  while(NewRemainder[NewRemainder.Length()] == ',')
11959  {
11960  if(NewRemainder.Length() > 1)
11961  {
11962  NewRemainder = NewRemainder.SubString(1, NewRemainder.Length() - 1);
11963  }
11964  else
11965  {
11966  NewRemainder = "";
11967  break;
11968  }
11969  }
11970  // check if zero length & fail if so
11971  if(NewRemainder == "")
11972  {
11973  TimetableMessage(GiveMessages, "Error in timetable - no events following train: '" + OneEntry + "'");
11974  Utilities->CallLogPop(769);
11975  return(false);
11976  }
11977  // now have one more entry than there are commas
11978  int CommaCount = 0;
11979  for(int x = 1; x < NewRemainder.Length() + 1; x++)
11980  {
11981  if(NewRemainder[x] == ',')
11982  {
11983  CommaCount++;
11984  }
11985  } // must have at least 1 comma, for start & finish entries, unless train is entered under signaller control
11986  if(CommaCount == 0)
11987  {
11988  if((NewRemainder.SubString(7, 3) != "Snt") || (NewRemainder[NewRemainder.Length()] != 'S'))
11989  {
11990  int SubStringLength = 20;
11991  if(OneEntry.Length() < 20)
11992  {
11993  SubStringLength = OneEntry.Length();
11994  }
11995  TimetableMessage(GiveMessages,
11996  "Error in timetable - must have at least a start and a finish event for a train that is not started under signaller control - see line beginning: '" +
11997  OneEntry.SubString(1, SubStringLength) + "'....");
11998  Utilities->CallLogPop(783);
11999  return(false);
12000  }
12001  }
12002 
12003  AnsiString OneLine = "";
12004  TTimetableFormatType FormatType;
12005  TTimetableSequenceType SequenceType;
12006  TTimetableLocationType LocationType;
12007  TTimetableShuttleLinkType ShuttleLinkType;
12008  bool FinishFlag = false;
12009 // bool NewTrain = false;//added at v2.14.0 to record created trains for later zero power checks <-- not needed after zero power restriction dropped
12010  for(int x = 0; x < CommaCount + 1; x++)
12011  {
12012  if((CommaCount == 0) || (x < CommaCount))
12013  // i.e. train entered under signaller control with no repeats, or entry is not the last,
12014  // in which case there's a comma & finish element or repeat still to come this entry could
12015  // be a finish but can't be a repeat
12016  {
12017  if(CommaCount == 0)
12018  {
12019  OneLine = NewRemainder;
12020  NewRemainder = "";
12021  }
12022  else
12023  {
12024  Pos = NewRemainder.Pos(',');
12025  OneLine = NewRemainder.SubString(1, Pos - 1);
12026  NewRemainder = NewRemainder.SubString(Pos + 1, NewRemainder.Length() - Pos);
12027  }
12028  First = "";
12029  Second = "";
12030  Third = "";
12031  Fourth = "";
12032  RearStartOrRepeatMins = 0;
12033  FrontStartOrRepeatDigits = 0;
12034  NumberOfRepeats = 0;
12035  if(!SplitEntry(0, OneLine, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
12036  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
12037  {
12038  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneLine + "'");
12039  Utilities->CallLogPop(756);
12040  return(false);
12041  }
12042 /* not needed at v2.19.0
12043  if((Second == "Snt") || (Second == "Snt-sh")) //added at v2.14.0, see above
12044  {
12045  NewTrain = true; not needed when zero power restrictions removed
12046  }
12047 */
12048  //below added at v2.14.0 to prevent unpowered trains attempting to be joined by (Second == jbo), split (Second -- fsp or rsp),
12049  //or change direction. Form a new service dealt with below for zero power as it's a finish event. <-- removed at v2.19.0
12050 
12051 /* restriction removed at v2.19.0
12052  if(NewTrain && (PowerAtRail < 1) && (Second == "jbo"))
12053  {
12054  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12055  "': a train created without power can't 'be joined by' another train (i.e. can't include command 'jbo'), "
12056  "use command 'Fjo' (i.e. 'join' another train) instead immediately after the line containing 'Snt', and use "
12057  "command 'jbo' for the train it is to join.");
12058  Utilities->CallLogPop(2545);
12059  return(false);
12060  }
12061  if(NewTrain && (PowerAtRail < 1) && ((Second == "fsp") || (Second == "rsp")))
12062  {
12063  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12064  "': a train created without power can't split.");
12065  Utilities->CallLogPop(2546);
12066  return(false);
12067  }
12068  if(NewTrain && (PowerAtRail < 1) && (Second == "cdt"))
12069  {
12070  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12071  "': a train created without power can't change direction under timetable control.");
12072  Utilities->CallLogPop(2547);
12073  return(false);
12074  }
12075 */
12076  //end of new additions
12077  if(x == 0) // should be start event
12078  {
12079  if(SequenceType != StartSequence)
12080  {
12081  TimetableMessage(GiveMessages, "Error in timetable - First event not a start event: '" + OneLine + "'");
12082  Utilities->CallLogPop(784);
12083  return(false);
12084  }
12085  if((Second == "Snt") && (Fourth == 'S') && (NewRemainder != ""))
12086  {
12087  if(NewRemainder[1] != 'R')
12088  {
12089  TimetableMessage(GiveMessages,
12090  "Error in timetable - the only event that can follow a train created under signaller control is a repeat, see '" +
12091  OneLine + "'");
12092  Utilities->CallLogPop(787);
12093  return(false);
12094  }
12095  }
12096  if((Second == "Snt") || (Second == "Snt-sh"))
12097  // need full train information including non-default values for at least HeadCode, Description,
12098  // MaxRunningSpeed, Mass, MaxBrakeRate, & PowerAtRail
12099  {
12100  if((HeadCode == "") || (Description == "") || (MaxRunningSpeed == 0) || (Mass == 0) || (MaxBrakeRate == 0)) // ||
12101  // (PowerAtRail == 0)) allowed 0 for power at v2.4.0
12102  {
12103  TimetableMessage(GiveMessages, "Error in timetable - train information incomplete before 'Snt' or 'Snt-sh' start event: '" +
12104  OneLine + "'");
12105  Utilities->CallLogPop(1783);
12106  return(false);
12107  }
12108  }
12109  if((Second == "Sfs") || (Second == "Sns") || (Second == "Sns-sh") || (Second == "Sns-fsh"))
12110  // service continuation - need at least non-default value for HeadCode
12111  {
12112  if(HeadCode == "")
12113  {
12114  TimetableMessage(GiveMessages, "Error in timetable - headcode missing before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
12115  OneLine + "'");
12116  Utilities->CallLogPop(788);
12117  return(false);
12118  }
12119  if((StartSpeed != 0) || (MaxRunningSpeed != 0) || (Mass != 0) || (MaxBrakeRate != 0) || (PowerAtRail != 0))
12120  {
12121  TimetableMessage(GiveMessages,
12122  "Error in timetable - information additional to a headcode & optional description given before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
12123  OneLine + "'");
12124  Utilities->CallLogPop(843);
12125  return(false);
12126  }
12127  }
12128  }
12129  if(SequenceType == FinishSequence)
12130  {
12131  FinishFlag = true;
12132  // marker for only permitted additional entry being a repeat, only needed if the
12133  // finish entry is not the last entry
12134  }
12135  if(FinalCall)
12136  {
12137  // interpret & add to ActionVector
12138  TDateTime TempTime;
12139  TActionVectorEntry ActionVectorEntry;
12140  ActionVectorEntry.FormatType = FormatType;
12141  ActionVectorEntry.LocationType = LocationType;
12142  ActionVectorEntry.SequenceType = SequenceType;
12143  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
12144  ActionVectorEntry.Warning = Warning;
12145  if(FormatType == TimeLoc)
12146  {
12147  if(CheckTimeValidity(1, First, ActionVectorEntry.EventTime))
12148  {
12149  ;
12150  } // these will all be true as final call
12151  ActionVectorEntry.LocationName = Second;
12152  if(Third == "") //added at v2.23.0
12153  {
12154  ActionVectorEntry.MinDwellTime = 30.0;
12155  }
12156  else
12157  {
12158  ActionVectorEntry.MinDwellTime = Third.ToDouble();
12159  }
12160  }
12161  else if(FormatType == PassTime) // new
12162  {
12163  if(CheckTimeValidity(17, First, ActionVectorEntry.EventTime))
12164  {
12165  ;
12166  }
12167  ActionVectorEntry.Command = Second;
12168  ActionVectorEntry.LocationName = Third;
12169  }
12170  else if(FormatType == TimeTimeLoc)
12171  {
12172  if(CheckTimeValidity(2, First, ActionVectorEntry.ArrivalTime))
12173  {
12174  ;
12175  }
12176  if(CheckTimeValidity(3, Second, ActionVectorEntry.DepartureTime))
12177  {
12178  ;
12179  }
12180  ActionVectorEntry.LocationName = Third;
12181  if(Fourth == "") //added at v2.23.0
12182  {
12183  ActionVectorEntry.MinDwellTime = 30.0;
12184  }
12185  else
12186  {
12187  ActionVectorEntry.MinDwellTime = Fourth.ToDouble();
12188  }
12189  }
12190  else if(FormatType == TimeCmd)
12191  {
12192  if(CheckTimeValidity(4, First, ActionVectorEntry.EventTime))
12193  {
12194  ;
12195  }
12196  ActionVectorEntry.Command = Second;
12197  }
12198  else if(FormatType == ExitRailway)
12199  {
12200  if(CheckTimeValidity(18, First, ActionVectorEntry.EventTime))
12201  {
12202  ;
12203  }
12204  ActionVectorEntry.Command = Second;
12205  ActionVectorEntry.ExitList = ExitList;
12206  }
12207  else if(FormatType == StartNew)
12208  {
12209  if(CheckTimeValidity(5, First, ActionVectorEntry.EventTime))
12210  {
12211  ;
12212  }
12213  ActionVectorEntry.Command = Second;
12214  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12215  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12216  if(Fourth == 'S')
12217  {
12218  ActionVectorEntry.SignallerControl = true;
12219  }
12220  }
12221  else if(FormatType == SNTShuttle)
12222  {
12223  if(CheckTimeValidity(6, First, ActionVectorEntry.EventTime))
12224  {
12225  ;
12226  }
12227  ActionVectorEntry.Command = Second;
12228  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12229  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12230  ActionVectorEntry.OtherHeadCode = Fourth;
12231  }
12232  else if(FormatType == SNSShuttle)
12233  {
12234  if(CheckTimeValidity(7, First, ActionVectorEntry.EventTime))
12235  {
12236  ;
12237  }
12238  ActionVectorEntry.Command = Second;
12239  ActionVectorEntry.OtherHeadCode = Third;
12240  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12241  }
12242  else if(FormatType == TimeCmdHeadCode) //fsp/rsp is TimeCmdHeadCode but may have a Fourth - see below
12243  {
12244  if(CheckTimeValidity(8, First, ActionVectorEntry.EventTime))
12245  {
12246  ;
12247  }
12248  ActionVectorEntry.Command = Second;
12249  ActionVectorEntry.OtherHeadCode = Third;
12250  if((Second == "fsp") || (Second == "rsp")) //new at v2.15.0 & checked in SplitEntry
12251  {
12252  ActionVectorEntry.SplitDistribution = "50-50"; //added at v2.18.0
12253  if(Fourth != "")
12254  {
12255  ActionVectorEntry.SplitDistribution = Fourth;
12256  }
12257  }
12258  }
12259  else if((FormatType == FNSNonRepeatToShuttle) || (FormatType == SNSNonRepeatFromShuttle))
12260  {
12261  if(CheckTimeValidity(9, First, ActionVectorEntry.EventTime))
12262  {
12263  ;
12264  }
12265  ActionVectorEntry.Command = Second;
12266  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12267  }
12268  else if(FormatType == FSHNewService)
12269  {
12270  if(CheckTimeValidity(10, First, ActionVectorEntry.EventTime))
12271  {
12272  ;
12273  }
12274  ActionVectorEntry.Command = Second;
12275  ActionVectorEntry.OtherHeadCode = Third;
12276  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12277  }
12278  else if(FormatType == FinRemHere)
12279  {
12280  ActionVectorEntry.Command = Second;
12281  }
12282  else if(FormatType == TimeCmdDescription) //new at v2.15.0
12283  {
12284  if(CheckTimeValidity(35, First, ActionVectorEntry.EventTime))
12285  {
12286  ;
12287  }
12288  ActionVectorEntry.Command = Second;
12289  ActionVectorEntry.NewDescription = Third;
12290  }
12291  else if(FormatType == TimeCmdMaxSpeed) //new at v2.21.0
12292  {
12293  if(CheckTimeValidity(45, First, ActionVectorEntry.EventTime))
12294  {
12295  ;
12296  }
12297  ActionVectorEntry.Command = Second;
12298  ActionVectorEntry.NewMaxSpeed = Third;
12299  }
12300  ActionVectorEntry.OneLineText = OneLine;
12301  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12302  }
12303  }
12304  else // last entry, & not entered under signaller control with no repeats, i.e. could be finish or repeat
12305  {
12306  OneLine = NewRemainder;
12307  First = "";
12308  Second = "";
12309  Third = "";
12310  Fourth = "";
12311  RearStartOrRepeatMins = 0;
12312  FrontStartOrRepeatDigits = 0;
12313  NumberOfRepeats = 0;
12314  if((FinishFlag) && (OneLine[1] != 'R'))
12315  // already had a finish entry
12316  {
12317  TimetableMessage(GiveMessages, "Error in timetable - Last event = '" + OneLine + "'. An earlier finish event has been found with something other than a repeat following it - only a repeat can follow a finish event.");
12318  Utilities->CallLogPop(79);
12319  return(false);
12320  }
12321  if(OneLine[1] != 'R') // must be finish
12322  {
12323  if(!SplitEntry(1, OneLine, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
12324  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
12325  {
12326  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneLine + "'.\nIf the program version is not the latest the "
12327  "timetable may have features that aren't compatible with the version in use.");
12328  Utilities->CallLogPop(757);
12329  return(false);
12330  }
12331  //below added at v2.14.0 to prevent unpowered trains attempting to form a new service.
12332 /* restriction removed at v2.19.0
12333  if(NewTrain && (PowerAtRail < 1) && ((Second == "Fns") || (Second == "Frh-sh") || (Second == "Fns-sh") || (Second == "F-nshs")))
12334  {
12335  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12336  "': a train created without power can't form a new service.");
12337  Utilities->CallLogPop(2548);
12338  return(false);
12339  }
12340  //end of new additions
12341 */
12342  // check if warning for Frh or Fjo & reject
12343  if(Warning && (Second == "Frh"))
12344  {
12345  TimetableMessage(GiveMessages, "Error in line - '" + OneLine + "': warnings cannot be given for 'Frh' events");
12346  Utilities->CallLogPop(1793);
12347  return(false);
12348  }
12349  if(Warning && (Second == "Fjo"))
12350  {
12351  TimetableMessage(GiveMessages, "Error in line - '" + OneLine +
12352  "': warnings cannot be given for 'Fjo' events, for a train join warning add a 'W' prefix to the 'jbo' event");
12353  Utilities->CallLogPop(1794);
12354  return(false);
12355  }
12356 
12357  if(SequenceType != FinishSequence)
12358  {
12359  TimetableMessage(GiveMessages, "Error in timetable - last event should be a finish: '" + OneLine + "'");
12360  Utilities->CallLogPop(785);
12361  return(false);
12362  }
12363  if(FinalCall)
12364  {
12365  // interpret & add to ActionVector
12366  TDateTime TempTime;
12367  TActionVectorEntry ActionVectorEntry;
12368  ActionVectorEntry.FormatType = FormatType;
12369  ActionVectorEntry.LocationType = LocationType;
12370  ActionVectorEntry.SequenceType = SequenceType;
12371  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
12372  ActionVectorEntry.Warning = Warning;
12373  if(FormatType == TimeCmd)
12374  {
12375  if(CheckTimeValidity(11, First, ActionVectorEntry.EventTime))
12376  {
12377  ;
12378  }
12379  ActionVectorEntry.Command = Second;
12380  }
12381  else if(FormatType == TimeCmdHeadCode)
12382  {
12383  if(CheckTimeValidity(12, First, ActionVectorEntry.EventTime))
12384  {
12385  ;
12386  }
12387  ActionVectorEntry.Command = Second;
12388  ActionVectorEntry.OtherHeadCode = Third;
12389  }
12390  else if(FormatType == FNSNonRepeatToShuttle)
12391  {
12392  if(CheckTimeValidity(13, First, ActionVectorEntry.EventTime))
12393  {
12394  ;
12395  }
12396  ActionVectorEntry.Command = Second;
12397  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12398  }
12399  else if(FormatType == FSHNewService)
12400  {
12401  if(CheckTimeValidity(14, First, ActionVectorEntry.EventTime))
12402  {
12403  ;
12404  }
12405  ActionVectorEntry.Command = Second;
12406  ActionVectorEntry.OtherHeadCode = Third;
12407  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12408  }
12409  else if(FormatType == ExitRailway)
12410  {
12411  if(CheckTimeValidity(19, First, ActionVectorEntry.EventTime))
12412  {
12413  ;
12414  }
12415  ActionVectorEntry.Command = Second;
12416  ActionVectorEntry.ExitList = ExitList;
12417  }
12418  else if(FormatType == FinRemHere)
12419  {
12420  ActionVectorEntry.Command = Second;
12421  }
12422  ActionVectorEntry.OneLineText = OneLine;
12423  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12424  }
12425  }
12426  else // repeat
12427  {
12428  if(!SplitRepeat(0, OneLine, RearStartOrRepeatMins, FrontStartOrRepeatDigits, NumberOfRepeats, GiveMessages))
12429  {
12430  Utilities->CallLogPop(786);
12431  // error messages given in SplitRepeat
12432  return(false);
12433  }
12434  if(FinalCall)
12435  {
12436  TActionVectorEntry ActionVectorEntry;
12437  ActionVectorEntry.FormatType = Repeat;
12438  ActionVectorEntry.LocationType = LocTypeForRepeatEntry;
12439  ActionVectorEntry.SequenceType = SequTypeForRepeatEntry;
12440  ActionVectorEntry.ShuttleLinkType = ShuttleLinkTypeForRepeatEntry;
12441  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12442  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12443  ActionVectorEntry.NumberOfRepeats = NumberOfRepeats;
12444  ActionVectorEntry.OneLineText = OneLine;
12445  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12446  }
12447  }
12448  }
12449  }
12450  if(FinalCall)
12451  {
12452  TrainDataVector.push_back(TempTrainDataEntry);
12453  }
12454  }
12455  Utilities->CallLogPop(80);
12456  return(true);
12457 }
12458 
12459 // ---------------------------------------------------------------------------
12460 
12461 bool TTrainController::Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
12462 {
12463  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Last2CharactersBothDigits," + HeadCode);
12464  if((HeadCode[HeadCode.Length() - 1] < '0') || (HeadCode[HeadCode.Length() - 1] > '9'))
12465  {
12466  Utilities->CallLogPop(1890);
12467  return(false);
12468  }
12469  if((HeadCode[HeadCode.Length()] < '0') || (HeadCode[HeadCode.Length()] > '9'))
12470  {
12471  Utilities->CallLogPop(1891);
12472  return(false);
12473  }
12474  Utilities->CallLogPop(1892);
12475  return(true);
12476 }
12477 
12478 // ---------------------------------------------------------------------------
12479 
12480 bool TTrainController::CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
12481 // 1st 5 chars must be HH:MM, anything else will be ignored
12482 {
12483  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckTimeValidity," + TimeStr);
12484  if(TimeStr.Length() < 5)
12485  {
12486  Utilities->CallLogPop(926);
12487  return(false);
12488  }
12489  if((TimeStr[1] < '0') || (TimeStr[1] > '9'))
12490  {
12491  Utilities->CallLogPop(927);
12492  return(false);
12493  }
12494  if((TimeStr[2] < '0') || (TimeStr[2] > '9'))
12495  {
12496  Utilities->CallLogPop(928);
12497  return(false);
12498  }
12499  if(TimeStr[3] != ':')
12500  {
12501  Utilities->CallLogPop(929);
12502  return(false);
12503  }
12504  if((TimeStr[4] < '0') || (TimeStr[4] > '5'))
12505  {
12506  Utilities->CallLogPop(930);
12507  return(false);
12508  }
12509  if((TimeStr[5] < '0') || (TimeStr[5] > '9'))
12510  {
12511  Utilities->CallLogPop(931);
12512  return(false);
12513  }
12514  while(TimeStr.Length() > 5)
12515  {
12516  TimeStr = TimeStr.SubString(1, TimeStr.Length() - 1);
12517  }
12518  double WholeHours = (AnsiString(TimeStr[1]) + AnsiString(TimeStr[2])).ToDouble();
12519  double FracHour = ((AnsiString(TimeStr[4]) + AnsiString(TimeStr[5])).ToDouble()) / 60.0;
12520 
12521  if((WholeHours + FracHour) >= 95.98334)
12522  {
12523  Utilities->CallLogPop(1817);
12524  return(false); // > 95h 59m
12525  }
12526  Time = TDateTime((WholeHours + FracHour) / 24);
12527  Utilities->CallLogPop(932);
12528  return(true);
12529 }
12530 
12531 // ---------------------------------------------------------------------------
12532 
12533 bool TTrainController::SplitEntry(int Caller, AnsiString OneLine, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second,
12534  AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, TTimetableFormatType &FormatType,
12535  TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
12536 /* This is a train action entry from a single line of the timetable, i.e. not train information and not a repeat entry.
12537  Return false for failure.
12538  See description above under ProcessOneTimetableLinefor details of train action entries
12539  NB all types set except LocationType for Snt as may be located or not
12540 */{
12541  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitEntry," + OneLine);
12542  Warning = false;
12543  TDateTime TempTime;
12544 
12545  if(OneLine.Length() > 0)
12546  {
12547  if(OneLine[1] == 'W') // warning
12548  {
12549  Warning = true;
12550  OneLine = OneLine.SubString(2, OneLine.Length() - 1);
12551  // strip it off
12552  }
12553  }
12554  if(OneLine == "Frh")
12555  {
12556  FormatType = FinRemHere;
12557  SequenceType = FinishSequence;
12558  LocationType = AtLocation;
12559  ShuttleLinkType = NotAShuttleLink;
12560  Second = "Frh";
12561  Utilities->CallLogPop(1016);
12562  return(true);
12563  }
12564  if(OneLine.Length() < 7)
12565  {
12566  Utilities->CallLogPop(907);
12567  return(false); // 'HH:MM;' + at least a one-letter location name
12568  }
12569  int Pos = OneLine.Pos(';'); // first segment delimiter
12570 
12571  if(Pos != 6)
12572  {
12573  Utilities->CallLogPop(908);
12574  return(false);
12575  // no delimiter or delimiter not in position 6, has to be a time so fail
12576  }
12577  First = OneLine.SubString(1, 5); // has to be a time
12578  if(!CheckTimeValidity(16, First, TempTime))
12579  {
12580  Utilities->CallLogPop(909);
12581  return(false);
12582  }
12583  AnsiString Remainder = OneLine.SubString(Pos + 1, OneLine.Length() - Pos);
12584 
12585 // if((Remainder[1] >= '0') && (Remainder[1] <= '9')) changed at v2.16.0 so only 'digit-digit-colon....' interpreted as a time - to allow locations to begin with digits
12586  if((Remainder.Length() >= 3) && (Remainder[1] >= '0') && (Remainder[1] <= '9') && (Remainder[2] >= '0') && (Remainder[2] <= '9') && (Remainder[3] == ':'))
12587  // next segment is a time so this is a TimeTimeLoc & 3rd seg has to be a location to be valid
12588  {
12589  if(Remainder.Length() < 7)
12590  {
12591  Utilities->CallLogPop(910);
12592  return(false); // 'HH:MM;' + at least a one-letter location name
12593  }
12594  Pos = Remainder.Pos(';'); // second segment delimiter
12595  if(Pos == 0)
12596  {
12597  Utilities->CallLogPop(911);
12598  return(false);
12599  // no delimiter, has to be one between departure time & location
12600  }
12601  Second = Remainder.SubString(1, 5); // has to be a time
12602  if(!CheckTimeValidity(15, Second, TempTime))
12603  {
12604  Utilities->CallLogPop(912);
12605  return(false);
12606  }
12607  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos); //may be a fourth = non-default dwell time
12608  Pos = Third.Pos(';');
12609  if(Pos != 0) //there is a fourth
12610  {
12611  Fourth = Third.SubString(Pos + 1, Remainder.Length() - Pos); //min dwell time
12612  if(Fourth == "")
12613  {
12614  Utilities->CallLogPop(2731);
12615  return(false);
12616  }
12617  Third = Third.SubString(1, Pos - 1); //location
12618  for(int x = 1; x <= Fourth.Length(); x++)
12619  {
12620  if((Fourth[x] < '0') || (Fourth[x] > '9'))
12621  {
12622  TimetableMessage(GiveMessages, "Invalid character in minimum dwell time in " + OneLine + ". Must be a whole number of seconds.");
12623  Utilities->CallLogPop(2732);
12624  return(false);
12625  }
12626  }
12627  if(Fourth.ToDouble() < 29.9)
12628  {
12629  TimetableMessage(GiveMessages, "Error in timetable - a minimum dwell time can't be less than 30 seconds: '" + OneLine + "'");
12630  Utilities->CallLogPop(2733);
12631  return(false);
12632  }
12633  }
12634  if(!CheckLocationValidity(0, Third, GiveMessages, CheckLocationsExistInRailway))
12635  {
12636  Utilities->CallLogPop(913);
12637  return(false);
12638  }
12639  FormatType = TimeTimeLoc;
12640  SequenceType = IntermediateSequence;
12641  LocationType = AtLocation;
12642  ShuttleLinkType = NotAShuttleLink;
12643  Utilities->CallLogPop(914);
12644  return(true);
12645  }
12646  Pos = Remainder.Pos(';'); // second segment delimiter
12647  if(Pos == 0) // no third segment so second must be a location, or cdt
12648  {
12649  Second = Remainder;
12650  if(Second == "cdt")
12651  {
12652  FormatType = TimeCmd;
12653  ShuttleLinkType = NotAShuttleLink;
12654  LocationType = AtLocation;
12655  SequenceType = IntermediateSequence;
12656  Utilities->CallLogPop(915);
12657  return(true);
12658  }
12659  if(!CheckLocationValidity(1, Second, GiveMessages, CheckLocationsExistInRailway))
12660  {
12661  Utilities->CallLogPop(916);
12662  return(false);
12663  }
12664  else
12665  {
12666  FormatType = TimeLoc; //TimeLoc with default dwell time
12667  LocationType = AtLocation;
12668  SequenceType = IntermediateSequence;
12669  ShuttleLinkType = NotAShuttleLink;
12670  Utilities->CallLogPop(917);
12671  return(true);
12672  }
12673  }
12674 //check here for TimeLoc with non-default dwell time
12675  if((Pos != 0) && (CheckLocationValidity(4, Remainder.SubString(1, Pos - 1), false, CheckLocationsExistInRailway))) //false for no give messages as likely to be ok
12676  {
12677  Second = Remainder.SubString(1, Pos - 1); //location
12678  if(NotACommand(0, Second)) //added at v2.23.0 so invert works as that permitted without a railway loaded so any text (including commands) can count as locations
12679  {
12680  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos); //non-default dwell time
12681  if(Third == "")
12682  {
12683  Utilities->CallLogPop(2734);
12684  return(false);
12685  }
12686  for(int x = 1; x <= Third.Length(); x++)
12687  {
12688  if((Third[x] < '0') || (Third[x] > '9'))
12689  {
12690  TimetableMessage(GiveMessages, "Invalid character in minimum dwell time in " + OneLine + ". Must be a whole number of seconds.");
12691  Utilities->CallLogPop(2735);
12692  return(false);
12693  }
12694  }
12695  if(Third.ToDouble() < 29.9)
12696  {
12697  TimetableMessage(GiveMessages, "Error in timetable - a minimum dwell time can't be less than 30 seconds: '" + OneLine + "'");
12698  Utilities->CallLogPop(2736);
12699  return(false);
12700  }
12701  FormatType = TimeLoc; //TimeLoc with default dwell time
12702  LocationType = AtLocation;
12703  SequenceType = IntermediateSequence;
12704  ShuttleLinkType = NotAShuttleLink;
12705  Utilities->CallLogPop(2737);
12706  return(true);
12707  }
12708  }
12709  // here if second segment is a command, with a third & maybe fourth segments
12710  if((Pos != 4) && (Pos != 7) && (Pos != 8))
12711  {
12712  Utilities->CallLogPop(918);
12713  return(false);
12714  // no third segement or not in position 4 or 7, & should be since all commands are 3, 6 or 7 letters
12715  }
12716  Second = Remainder.SubString(1, Pos - 1); // command
12717 
12718  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12719  // details
12720  Pos = Remainder.Pos(';'); // third segment delimiter
12721  if(Pos == 0)
12722  {
12723  Third = Remainder;
12724  }
12725  else
12726  {
12727  Third = Remainder.SubString(1, Pos - 1);
12728  Fourth = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12729  }
12730 
12731  if((Second == "Snt") || (Second == "Snt-sh"))
12732  // third has to be 2 element idents with a space between
12733  {
12734  int SpacePos = Third.Pos(' ');
12735  if(SpacePos == 0)
12736  {
12737  Utilities->CallLogPop(919);
12738  return(false); // no space
12739  }
12740  AnsiString RearStartStr = Third.SubString(1, SpacePos - 1);
12741  AnsiString FrontStartStr = Third.SubString(SpacePos + 1, Third.Length() - SpacePos);
12742  // int RearPosition=0, FrontPosition=0, RearExitPos=0;
12743  if(CheckLocationsExistInRailway)
12744  {
12745  if(!CheckStartPositionValidity(0, RearStartStr, FrontStartStr, GiveMessages))
12746  {
12747  Utilities->CallLogPop(920);
12748  return(false);
12749  }
12750  RearStartOrRepeatMins = Track->GetTrackVectorPositionFromString(3, RearStartStr, GiveMessages);
12751  FrontStartOrRepeatDigits = Track->GetTrackVectorPositionFromString(4, FrontStartStr, GiveMessages);
12752  }
12753  if(Second == "Snt")
12754  {
12755  FormatType = StartNew;
12756  SequenceType = StartSequence;
12757  LocationType = NoLocation;
12758  // can't be set until know whether located or not - done in SecondPassActions
12759  ShuttleLinkType = NotAShuttleLink;
12760  }
12761  else // Snt-sh
12762  {
12763  FormatType = SNTShuttle;
12764  LocationType = AtLocation;
12765  SequenceType = StartSequence;
12766  ShuttleLinkType = ShuttleLink;
12767  if(!CheckHeadCodeValidity(0, GiveMessages, Fourth))
12768  {
12769  Utilities->CallLogPop(1038);
12770  return(false);
12771  }
12772  }
12773  Utilities->CallLogPop(921);
12774  return(true);
12775  }
12776  if(Second == "Sns-sh") // third & fourth have to be headcodes
12777  {
12778  FormatType = SNSShuttle;
12779  LocationType = AtLocation;
12780  SequenceType = StartSequence;
12781  ShuttleLinkType = ShuttleLink;
12782  if(!CheckHeadCodeValidity(1, GiveMessages, Third))
12783  {
12784  Utilities->CallLogPop(1039);
12785  return(false);
12786  }
12787  if(!CheckHeadCodeValidity(2, GiveMessages, Fourth))
12788  {
12789  Utilities->CallLogPop(1040);
12790  return(false);
12791  }
12792  Utilities->CallLogPop(1041);
12793  return(true);
12794  }
12795  if(Second == "F-nshs")
12796  {
12797  FormatType = FNSNonRepeatToShuttle;
12798  LocationType = AtLocation;
12799  SequenceType = FinishSequence;
12800  ShuttleLinkType = ShuttleLink;
12801  if(!CheckHeadCodeValidity(3, GiveMessages, Third))
12802  {
12803  Utilities->CallLogPop(1047);
12804  return(false);
12805  }
12806  Utilities->CallLogPop(1048);
12807  return(true);
12808  }
12809  if(Second == "Sns-fsh")
12810  {
12811  FormatType = SNSNonRepeatFromShuttle;
12812  LocationType = AtLocation;
12813  SequenceType = StartSequence;
12814  ShuttleLinkType = ShuttleLink;
12815  if(!CheckHeadCodeValidity(4, GiveMessages, Third))
12816  {
12817  Utilities->CallLogPop(1098);
12818  return(false);
12819  }
12820  Utilities->CallLogPop(1099);
12821  return(true);
12822  }
12823  if(Second == "Fns-sh") // third & fourth have to be headcodes
12824  {
12825  FormatType = FSHNewService;
12826  LocationType = AtLocation;
12827  SequenceType = FinishSequence;
12828  ShuttleLinkType = ShuttleLink;
12829  if(!CheckHeadCodeValidity(5, GiveMessages, Third))
12830  {
12831  Utilities->CallLogPop(1050);
12832  return(false);
12833  }
12834  if(!CheckHeadCodeValidity(6, GiveMessages, Fourth))
12835  {
12836  Utilities->CallLogPop(1051);
12837  return(false);
12838  }
12839  Utilities->CallLogPop(1052);
12840  return(true);
12841  }
12842  // new segment for 'pas'
12843  if(Second == "pas") // third has to be a location
12844  {
12845  FormatType = PassTime;
12846  LocationType = EnRoute;
12847  SequenceType = IntermediateSequence;
12848  ShuttleLinkType = NotAShuttleLink;
12849  if(!CheckLocationValidity(2, Third, GiveMessages, CheckLocationsExistInRailway))
12850  {
12851  Utilities->CallLogPop(1515);
12852  return(false);
12853  }
12854  Utilities->CallLogPop(1516);
12855  return(true);
12856  }
12857  // new segment for revised 'Fer'
12858  if(Second == "Fer")
12859  // third has to be a set of IDs separated by spaces, and at least 1
12860  {
12861  FormatType = ExitRailway;
12862  LocationType = EnRoute;
12863  SequenceType = FinishSequence;
12864  ShuttleLinkType = NotAShuttleLink;
12865  if(CheckLocationsExistInRailway)
12866  {
12867  if(!CheckAndPopulateListOfIDs(0, Third, ExitList, GiveMessages))
12868  {
12869  Utilities->CallLogPop(1519);
12870  return(false);
12871  }
12872  }
12873  Utilities->CallLogPop(1520);
12874  return(true);
12875  }
12876  if(Second == "dsc") //new at v2.15.0 - change description
12877  {
12878  if(Third.Length() > 60)
12879  {
12880  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters in '" + Third + "'");
12881  Utilities->CallLogPop(2582);
12882  return(false);
12883  }
12884  for(int x = 1; x < Third.Length() + 1; x++)
12885  {
12886 // if((Third[x] < ' ') || (Third[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12887  if((Third[x] < ' ') && (Third[x] >= 0))
12888  {
12889  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + Third + "'");
12890  Utilities->CallLogPop(2583);
12891  return(false);
12892  }
12893  }
12894  FormatType = TimeCmdDescription;
12895  LocationType = AtLocation;
12896  SequenceType = IntermediateSequence;
12897  ShuttleLinkType = NotAShuttleLink;
12898  Utilities->CallLogPop(2604);
12899  return(true);
12900  }
12901  if(Second == "cms") //new at v2.21.0 - change max speed
12902  {
12903  if(Third == "")
12904  {
12905  TimetableMessage(GiveMessages, "New maximum speed value missing");
12906  Utilities->CallLogPop(2704);
12907  return(false);
12908  }
12909  for(int x = 1; x < Third.Length() + 1; x++)
12910  {
12911  if((Third[x] < '0') || (Third[x] > '9'))
12912  {
12913  TimetableMessage(GiveMessages, "Train maximum speed must be a number in '" + Third + "'");
12914  Utilities->CallLogPop(2705);
12915  return(false);
12916  }
12917  }
12918  int MaxRunningSpeed = Third.ToInt(); //must be a whole positive number here
12919  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12920  {
12921  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be greater than 400km/h");
12922  Utilities->CallLogPop(2706);
12923  return(false);
12924  }
12925  if(MaxRunningSpeed < 10)
12926  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12927  {
12928  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be less than 10km/h.");
12929  Utilities->CallLogPop(2707);
12930  return(false);
12931  }
12932  FormatType = TimeCmdMaxSpeed;
12933  LocationType = AtLocation;
12934  SequenceType = IntermediateSequence;
12935  ShuttleLinkType = NotAShuttleLink;
12936  Utilities->CallLogPop(2708);
12937  return(true);
12938  }
12939 
12940 // if((Second == "fsp") || (Second == "fsp")) then there can optionlly be a fourth: xx-yy where xx = percentage mass & yy = percentage power in the split train
12941 
12942  // all remainder must be TimeCmdHeadCode types to be valid
12943  if((Second != "Fns") && (Second != "Fjo") && (Second != "jbo") && (Second != "fsp") && (Second != "rsp") && (Second != "Sfs") && (Second != "Sns") &&
12944  (Second != "Frh-sh"))
12945  {
12946  Utilities->CallLogPop(922);
12947  return(false); // all TimeCmdHeadCode types
12948  }
12949  if(!CheckHeadCodeValidity(7, GiveMessages, Third))
12950  {
12951  Utilities->CallLogPop(923);
12952  return(false);
12953  }
12954  FormatType = TimeCmdHeadCode;
12955  LocationType = AtLocation;
12956  if(Second == "Frh-sh")
12957  {
12958  ShuttleLinkType = ShuttleLink;
12959  }
12960  else
12961  {
12962  ShuttleLinkType = NotAShuttleLink;
12963  }
12964  if((Second == "Fns") || (Second == "Fjo") || (Second == "Frh-sh"))
12965  {
12966  SequenceType = FinishSequence;
12967  }
12968  if((Second == "jbo") || (Second == "fsp") || (Second == "rsp"))
12969  {
12970  SequenceType = IntermediateSequence;
12971  }
12972  if((Second == "Sfs") || (Second == "Sns"))
12973  {
12974  SequenceType = StartSequence;
12975  }
12976  //new at v2.15.0 to allow splits to have different characteristics - Fourth specifies: AA-BB where AA & BB can be 1 or 2 digits, AA is percentage mass
12977  if((Fourth != "") && ((Second == "fsp") || (Second == "rsp"))) //& BB is percentage power allocated to the split off train
12978  {
12979  if(!CheckFourthValidityForSplit(Fourth, GiveMessages))
12980  {
12981  Utilities->CallLogPop(2584);
12982  return(false);
12983  }
12984  }
12985  Utilities->CallLogPop(924);
12986  return(true);
12987 }
12988 
12989 // ---------------------------------------------------------------------------
12990 
12991 bool TTrainController::NotACommand(int Caller, AnsiString Text)
12992 {
12993  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",NotACommand");
12994  if((Text == "Snt") || (Text == "Sfs") || (Text == "Sns") || (Text == "Sns-fsh") || (Text == "Snt-sh") || (Text == "Sns-sh") ||
12995  (Text == "pas") || (Text == "jbo") || (Text == "fsp") || (Text == "rsp") || (Text == "cdt") || (Text == "dsc") ||
12996  (Text == "cms") || (Text == "Fns") || (Text == "Fjo") || (Text == "Fer") || (Text == "Frh-sh") || (Text == "Fns-sh") ||
12997  (Text == "F-nshs") || (Text == "Frh"))
12998  {
12999  Utilities->CallLogPop(2738);
13000  return(false);
13001  }
13002  Utilities->CallLogPop(2739);
13003  return(true);
13004 }
13005 
13006 // ---------------------------------------------------------------------------
13007 
13008 bool TTrainController::CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages) //new at v2.15.0
13009 {
13010  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",CheckFourthValidityForSplit," + SplitDistributionString);
13011  bool ErrorFlag = false;
13012  int x, y;
13013  if((SplitDistributionString.Length() > 6 ) || (SplitDistributionString.Length() < 3))
13014  {
13015  ErrorFlag = true;
13016  }
13017  int pos = SplitDistributionString.Pos('-');
13018  if(pos == 0)
13019  {
13020  ErrorFlag = true;
13021  }
13022  else
13023  {
13024  AnsiString MassStr = SplitDistributionString.SubString(1, pos - 1);
13025  AnsiString PowerStr = SplitDistributionString.SubString(pos + 1, SplitDistributionString.Length() - pos);
13026  try //allows for one or two digit percentages
13027  {
13028  int x = MassStr.ToInt();
13029  int y = PowerStr.ToInt();
13030  if((x > 99) || (x < 1) || (y > 100) || (y < 0))
13031  {
13032  ErrorFlag = true;
13033  }
13034  }
13035  catch(const Exception &e) //non-error catch
13036  {
13037  ErrorFlag = true;
13038  }
13039  }
13040  if(ErrorFlag)
13041  {
13042  TimetableMessage(GiveMessages, "Error in split distribution " + SplitDistributionString + ", should be 'AA-BB' where AA is the percentage mass (min 1, max 99) and BB the percentage " +
13043  "power for the new split-off train");
13044  Utilities->CallLogPop(2585);
13045  return(false);
13046  }
13047  Utilities->CallLogPop(2601);
13048  return(true);
13049 }
13050 
13051 // ---------------------------------------------------------------------------
13052 
13053 bool TTrainController::CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
13054 {
13055  // check that the location name exists in the railway (only if CheckLocationsExistInRailway is true), doesn't begin with 'digit-digit-colon'
13056  // and contains no control characters changed at v2.16.0 to allow extended characters in location names
13057  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckLocationValidity," + LocStr);
13058  if(LocStr == "")
13059  {
13060  Utilities->CallLogPop(1353);
13061  return(false); // has to have at least one character
13062  }
13063 // if((LocStr[1] >= '0') && (LocStr[1] <= '9')) //changed at v2.16.0 to allow locations to begine with digits, if 'digit-digit-colon' then must be a time
13064  if((LocStr.Length() >= 3) && (LocStr[1] >= '0') && (LocStr[1] <= '9') && (LocStr[2] >= '0') && (LocStr[2] <= '9') && (LocStr[3] == ':'))
13065  {
13066  Utilities->CallLogPop(1354);
13067  return(false); // can't begin with 'digit-digit-colon' as this regarded as a time
13068  }
13069  for(int x = 1; x < LocStr.Length() + 1; x++)
13070  {
13071  if(((LocStr[x] < ' ') && (LocStr[x] >= 0)) || (LocStr[x] == ',') || (LocStr[x] == ';')) //changed at v2.16.0 to allow extended characters in location names
13072  {
13073  Utilities->CallLogPop(1355);
13074  return(false); // contains a special character or ',' or ';'
13075  }
13076 /*
13077  if(LocStr[x] > 'z') //dropped at v2.16.0 to allow extended characters in location names
13078  {
13079  Utilities->CallLogPop(1356);
13080  return(false); // contains a character outside the standard ASCII set
13081  }
13082 */
13083  }
13084  // check exists in railway location list if CheckLocationsExistInRailway is true
13085  if(CheckLocationsExistInRailway)
13086  {
13087  if(!Track->TimetabledLocationNameAllocated(3, LocStr))
13088  {
13089  TimetableMessage(GiveMessages, "Location name '" + LocStr +
13090  "' appears in the timetable but is not a valid name. To be valid the name must be a stopping location and apply to one or more platforms " +
13091  "(not concourses on their own), or to track at a blue non-station named location. BUT NOTE THAT trains can't stop at continuations so a name " +
13092  "that includes a continuation will not be valid.");
13093  Utilities->CallLogPop(1357);
13094  return(false);
13095  }
13096  }
13097  Utilities->CallLogPop(1358);
13098  return(true);
13099 }
13100 
13101 // ---------------------------------------------------------------------------
13102 
13103 bool TTrainController::CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
13104 {
13105  // if(!AnyHeadCodeValid) up to 8 characters total & last 4 characters must be NLNN where N = number and L = capital or small letter
13106  // if(AnyHeadCodeValid) up to 8 characters total, last 2 chars must be digits & last but 2 can be any alphanumeric, upper or lower case
13107  // NOTE: As of v0.6b AnyHeadCodeValid dropped, all headcodes are unrestricted
13108  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString((short)GiveMessages) + ",CheckHeadCodeValidity," +
13109  HeadCode);
13110  if((HeadCode.Length() < 4) || (HeadCode.Length() > 8))
13111  {
13112  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode +
13113  "', length must be between 4 and 8 characters, and last 4 must be a legitimate headcode. This error can also be caused by omitting a service reference after Sns, Snt-sh, Sns-sh, Fns, Fns-sh or Frh-sh");
13114  Utilities->CallLogPop(1359);
13115  return(false);
13116  }
13117  // firstly allow any printable character (ASCII >= CHAR(32) & <= CHAR(126)), as these allowed in 1st 4 characters
13118  for(int x = 1; x < (HeadCode.Length() + 1); x++)
13119  {
13120  if((HeadCode[x] < ' ') || (HeadCode[x] > '~'))
13121  {
13122  TimetableMessage(GiveMessages, "Non-printable character in headcode '" + HeadCode + "'");
13123  Utilities->CallLogPop(1895);
13124  return(false);
13125  }
13126  }
13127  // secondly ensure the true Headcode only has letters or digits
13128  for(int x = 3; x >= 0; x--)
13129  {
13130  if(((HeadCode[HeadCode.Length() - x] < 'A') || (HeadCode[HeadCode.Length() - x] > 'Z')) && ((HeadCode[HeadCode.Length() - x] < 'a') ||
13131  (HeadCode[HeadCode.Length() - x] > 'z')) && ((HeadCode[HeadCode.Length() - x] < '0') || (HeadCode[HeadCode.Length() - x] > '9')))
13132  {
13133  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode + "', headcode must consist of letters and digits only");
13134  Utilities->CallLogPop(1790);
13135  return(false);
13136  }
13137  }
13138  Utilities->CallLogPop(1364);
13139  return(true);
13140 }
13141 
13142 // ---------------------------------------------------------------------------
13143 
13144 bool TTrainController::CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
13145 // set of track element IDs, separated by spaces, and at least 1 present
13146 {
13147  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndPopulateListOfIDs," + IDSet); //had wrong title, changed at v2.13.0
13148  ExitList.clear();
13149  AnsiString CurrentID = "";
13150 
13151  if(IDSet.Length() == 0)
13152  {
13153  TimetableMessage(GiveMessages, "Must have at least one exit element ID following 'Fer'");
13154  Utilities->CallLogPop(1521);
13155  return(false);
13156  }
13157  for(int x = 1; x <= IDSet.Length(); x++)
13158  {
13159  char C = IDSet[x];
13160  if(((C < '0') || (C > '9')) && (C != ' ') && (C != 'N') && (C != '-'))
13161  {
13162  TimetableMessage(GiveMessages, "Illegal character in the set of element IDs following 'Fer' in '" + IDSet + "'");
13163  Utilities->CallLogPop(1522);
13164  return(false);
13165  }
13166 /* don't use, error checks in GetTrackVectorPositionFromString instead
13167  if(C == '-') //this section added at v2.13.0 because of Amon Sadler's error file submitted 24/03/22
13168  {
13169  if((x==1) || (x == IDSet.Length()))
13170  {
13171  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
13172  Utilities->CallLogPop(2479);
13173  return(false);
13174  }
13175  if((IDSet[x-1] < '0') || (IDSet[x-1] > '9') || (IDSet[x+1] < '0') || (IDSet[x+1] > '9'))
13176  {
13177  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
13178  Utilities->CallLogPop(2480);
13179  return(false);
13180  }
13181  }
13182 */
13183  }
13184  int Pos = IDSet.Pos(' '); // look for the first space
13185 
13186  while(true)
13187  {
13188  if(Pos == 0)
13189  {
13190  CurrentID = IDSet;
13191  IDSet = "";
13192  }
13193  else
13194  {
13195  CurrentID = IDSet.SubString(1, Pos - 1);
13196  IDSet = IDSet.SubString(Pos + 1, IDSet.Length() - Pos);
13197  }
13198  int VecPos = Track->GetTrackVectorPositionFromString(7, CurrentID, GiveMessages);
13199  if(VecPos == -1)
13200  {
13201  Utilities->CallLogPop(1523);
13202  return(false); // messages given in GetTrackVectorPositionFromString
13203  }
13204  else
13205  {
13206  if(Track->TrackElementAt(722, VecPos).TrackType != Continuation)
13207  {
13208  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' is not an exit");
13209  Utilities->CallLogPop(1524);
13210  return(false);
13211  }
13212  else
13213  {
13214  // first check for duplicates
13215  if(!ExitList.empty())
13216  {
13217  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
13218  {
13219  if(*ELIT == VecPos)
13220  {
13221  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' duplicates an earlier element");
13222  Utilities->CallLogPop(1532);
13223  return(false);
13224  }
13225  }
13226  }
13227  // of OK add it to the list
13228  ExitList.push_back(VecPos);
13229  }
13230  }
13231  if(IDSet == "")
13232  {
13233  Utilities->CallLogPop(1525);
13234  return(true);
13235  }
13236  else
13237  {
13238  Pos = IDSet.Pos(' '); // look for the next space
13239  }
13240  } // while(true)
13241 }
13242 
13243 // ---------------------------------------------------------------------------
13244 bool TTrainController::SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed,
13245  int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
13246 // 7 or 8 items for a new train (6 or 7 semicolons), for a continuing service only need headcode, though can have a description, if other
13247 // data entered for continuing service then will be ignored - message given to warn user, checks appropriate number of items and validity
13248 // of each item
13249 {
13250  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitTrainInfo," + TrainInfoStr);
13251  int Pos = 0;
13252  AnsiString Remainder = "";
13253  int SemiColonCount = 0;
13254 
13255  for(int x = 1; x < TrainInfoStr.Length() + 1; x++)
13256  {
13257  if(TrainInfoStr[x] == ';')
13258  {
13259  SemiColonCount++;
13260  }
13261  }
13262  if((SemiColonCount != 6) && (SemiColonCount != 7) && (SemiColonCount != 1) && (SemiColonCount != 0))
13263  {
13264  TimetableMessage(GiveMessages, "Error in train information in '" + TrainInfoStr +
13265  "'. Should be headcode + optional description for a continuing service;" +
13266  " or headcode, description, start speed, max running speed, mass, brake force, power (and optional signaller max. speed) for a new service");
13267  Utilities->CallLogPop(880);
13268  return(false);
13269  }
13270  if(SemiColonCount == 0)
13271  {
13272  HeadCode = TrainInfoStr;
13273  if(!CheckHeadCodeValidity(8, GiveMessages, HeadCode))
13274  {
13275  Utilities->CallLogPop(881);
13276  return(false);
13277  }
13278  Utilities->CallLogPop(882);
13279  return(true);
13280  }
13281  if(SemiColonCount == 1) // headcode & description only
13282  {
13283  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
13284  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
13285  Description = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
13286  if(!CheckHeadCodeValidity(9, GiveMessages, HeadCode))
13287  {
13288  Utilities->CallLogPop(883);
13289  return(false);
13290  }
13291  if(Description == "")
13292  {
13293  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
13294  Utilities->CallLogPop(884);
13295  return(false);
13296  }
13297  if(Description.Length() > 60)
13298  {
13299  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
13300  Utilities->CallLogPop(1157);
13301  return(false);
13302  }
13303  for(int x = 1; x < Description.Length() + 1; x++)
13304  {
13305 // if((Description[x] < ' ') || (Description[x] > '~')) changed at v2.16.0 to allow extended characters in location names
13306  if((Description[x] < ' ') && (Description[x] >= 0))
13307  {
13308  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
13309  Utilities->CallLogPop(885);
13310  return(false);
13311  }
13312  }
13313  Utilities->CallLogPop(886);
13314  return(true);
13315  }
13316  // if here must have 6 or 7 semicolons
13317  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
13318  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
13319  Remainder = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
13320  if(!CheckHeadCodeValidity(10, GiveMessages, HeadCode))
13321  {
13322  Utilities->CallLogPop(887);
13323  return(false);
13324  }
13325  Pos = Remainder.Pos(';'); // 2nd delimiter
13326  Description = Remainder.SubString(1, Pos - 1);
13327  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13328  if(Description == "")
13329  {
13330  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
13331  Utilities->CallLogPop(888);
13332  return(false);
13333  }
13334  if(Description.Length() > 60)
13335  {
13336  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
13337  Utilities->CallLogPop(1158);
13338  return(false);
13339  }
13340  for(int x = 1; x < Description.Length() + 1; x++)
13341  {
13342 // if((Description[x] < ' ') || (Description[x] > 126)) changed at v2.16.0 to allow extended characters in location names
13343  if((Description[x] < ' ') && (Description[x] >= 0))
13344  {
13345  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
13346  Utilities->CallLogPop(889);
13347  return(false);
13348  }
13349  }
13350  Pos = Remainder.Pos(';'); // 3rd delimiter
13351  AnsiString StartSpeedStr = Remainder.SubString(1, Pos - 1);
13352 
13353  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13354  if(StartSpeedStr == "")
13355  {
13356  TimetableMessage(GiveMessages, "Train starting speed missing in '" + TrainInfoStr + "'");
13357  Utilities->CallLogPop(890);
13358  return(false);
13359  }
13360  for(int x = 1; x < StartSpeedStr.Length() + 1; x++)
13361  {
13362  if((StartSpeedStr[x] < '0') || (StartSpeedStr[x] > '9'))
13363  {
13364  TimetableMessage(GiveMessages, "Train start speed contains invalid characters in '" + TrainInfoStr + "'");
13365  Utilities->CallLogPop(891);
13366  return(false);
13367  }
13368  }
13369  StartSpeed = StartSpeedStr.ToInt();
13370  if(StartSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13371  {
13372  StartSpeed = TTrain::MaximumSpeedLimit;
13373  if(!SSHigh) // added at v2.4.0
13374  {
13375  TimetableMessage(GiveMessages, "Train starting speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13376  SSHigh = true;
13377  }
13378  }
13379  Pos = Remainder.Pos(';'); // 4th delimiter
13380  AnsiString MaxRunningSpeedStr = Remainder.SubString(1, Pos - 1);
13381 
13382  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13383  if(MaxRunningSpeedStr == "")
13384  {
13385  TimetableMessage(GiveMessages, "Train maximum running speed missing in '" + TrainInfoStr + "'");
13386  Utilities->CallLogPop(892);
13387  return(false);
13388  }
13389  for(int x = 1; x < MaxRunningSpeedStr.Length() + 1; x++)
13390  {
13391  if((MaxRunningSpeedStr[x] < '0') || (MaxRunningSpeedStr[x] > '9'))
13392  {
13393  TimetableMessage(GiveMessages, "Train maximum running speed contains invalid characters in '" + TrainInfoStr + "'");
13394  Utilities->CallLogPop(893);
13395  return(false);
13396  }
13397  }
13398  MaxRunningSpeed = MaxRunningSpeedStr.ToInt();
13399  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13400  {
13401  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be greater than 400km/h");
13402  Utilities->CallLogPop(2709);
13403  return(false);
13404  }
13405  if(MaxRunningSpeed < 10)
13406  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13407  {
13408  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be less than 10km/h.");
13409  Utilities->CallLogPop(2710);
13410  return(false);
13411  }
13412  Pos = Remainder.Pos(';'); // 5th delimiter
13413  AnsiString MassStr = Remainder.SubString(1, Pos - 1);
13414 
13415  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13416  if(MassStr == "")
13417  {
13418  TimetableMessage(GiveMessages, "Train mass missing in '" + TrainInfoStr + "'");
13419  Utilities->CallLogPop(895);
13420  return(false);
13421  }
13422  for(int x = 1; x < MassStr.Length() + 1; x++)
13423  {
13424  if((MassStr[x] < '0') || (MassStr[x] > '9'))
13425  {
13426  TimetableMessage(GiveMessages, "Train mass contains invalid characters in '" + TrainInfoStr + "'");
13427  Utilities->CallLogPop(896);
13428  return(false);
13429  }
13430  }
13431  Mass = MassStr.ToInt() * 1000; // convert tonnes to kg
13432  if(Mass > TTrain::MaximumMassLimit) // 10,000tonnes
13433  {
13434  Mass = TTrain::MaximumMassLimit;
13435  if(!MassHigh) // added at v2.4.0
13436  {
13437  TimetableMessage(GiveMessages, "Train mass > 10,000 tonnes in '" + TrainInfoStr + "'. Setting it to 10,000 tonnes");
13438  MassHigh = true;
13439  }
13440  }
13441  if(Mass == 0)
13442  {
13443  TimetableMessage(GiveMessages, "Train mass zero in '" + TrainInfoStr + "'");
13444  Utilities->CallLogPop(897);
13445  return(false);
13446  }
13447  Pos = Remainder.Pos(';'); // 6th delimiter
13448  AnsiString MaxBrakeForceStr = Remainder.SubString(1, Pos - 1);
13449 
13450  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13451  if(MaxBrakeForceStr == "")
13452  {
13453  TimetableMessage(GiveMessages, "Train braking force missing in '" + TrainInfoStr + "'");
13454  Utilities->CallLogPop(898);
13455  return(false);
13456  }
13457  for(int x = 1; x < (MaxBrakeForceStr.Length() + 1); x++)
13458  {
13459  if((MaxBrakeForceStr[x] != '.') && ((MaxBrakeForceStr[x] < '0') || (MaxBrakeForceStr[x] > '9')))
13460  {
13461  TimetableMessage(GiveMessages, "Train braking force contains invalid characters in '" + TrainInfoStr + "'");
13462  Utilities->CallLogPop(899);
13463  return(false);
13464  }
13465  }
13466  double MaxBrakeForce = MaxBrakeForceStr.ToDouble() * 1000;
13467 
13468  // convert to kg force
13469  if((MaxBrakeForce / Mass) > 1) // gives 'g' braking - 9.81m/s/s
13470  {
13471  MaxBrakeForce = Mass;
13472  if(!BFHigh) // added at v2.4.0
13473  {
13474  TimetableMessage(GiveMessages, "Train braking force too high in '" + TrainInfoStr + "'. Setting it to the same as the train mass");
13475  BFHigh = true;
13476  }
13477  }
13478  if((MaxBrakeForce / Mass) < 0.01)
13479  {
13480  MaxBrakeForce = Mass * 0.01;
13481  if(!BFLow) // added at v2.4.0
13482  {
13483  TimetableMessage(GiveMessages, "Train braking force too low in '" + TrainInfoStr + "'. Setting it to 1% of the train mass");
13484  BFLow = true;
13485  }
13486  }
13487  // convert to m/s/s
13488  MaxBrakeRate = MaxBrakeForce / Mass * 9.81;
13489  // now may have just a power entry or power and signaller max. speed
13490  AnsiString GrossPowerStr = "", SignallerSpeedStr = "";
13491 
13492  if(SemiColonCount == 6)
13493  {
13494  GrossPowerStr = Remainder;
13495  SignallerSpeedStr = "30"; // default value
13496  }
13497  else // must be 7
13498  {
13499  Pos = Remainder.Pos(';'); // 7th delimiter
13500  GrossPowerStr = Remainder.SubString(1, Pos - 1);
13501  SignallerSpeedStr = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13502  }
13503  // deal with GrossPower
13504  if(GrossPowerStr == "")
13505  {
13506  TimetableMessage(GiveMessages, "Train power missing in '" + TrainInfoStr + "'");
13507  Utilities->CallLogPop(901);
13508  return(false);
13509  }
13510  for(int x = 1; x < GrossPowerStr.Length() + 1; x++)
13511  {
13512  if((GrossPowerStr[x] < '0') || (GrossPowerStr[x] > '9'))
13513  {
13514  TimetableMessage(GiveMessages, "Train power contains invalid characters in '" + TrainInfoStr + "'");
13515  Utilities->CallLogPop(902);
13516  return(false);
13517  }
13518  }
13519 
13520  double GrossPower = GrossPowerStr.ToInt() * 1000; // convert to W
13521 
13522  if(GrossPower > TTrain::MaximumPowerLimit) // 100MW
13523  {
13524  GrossPower = TTrain::MaximumPowerLimit;
13525  if(!PwrHigh)
13526  {
13527  TimetableMessage(GiveMessages, "Train power > 100,000kW in '" + TrainInfoStr + "'. Setting it to 100,000kW");
13528  PwrHigh = true;
13529  }
13530  }
13531  else if(GrossPower == 0) // changed at v2.4.0
13532  {
13533  GrossPower = 0.1;
13534  // can't be zero or AValue is zero and then have divide by zero error, so set to 0.1W so acceleration tiny (though should be intercepted before accel calculated)
13535  }
13536  else if((GrossPower > 0) && (GrossPower < 10000))
13537  // added at v2.4.0 to ensure min power of 8kW at rail unless zero (otherwise could have too low AValues
13538  {
13539  GrossPower = 10000;
13540  }
13541  PowerAtRail = GrossPower * 0.8;
13542  // apply ratio of 80% for rail to gross power (seems about average from an internet search)
13543 
13544  // deal with SignallerSpeed
13545  if(SignallerSpeedStr == "")
13546  {
13547  TimetableMessage(GiveMessages, "Signaller speed not set in '" + TrainInfoStr + "', either set a value or remove the extra semicolon");
13548  Utilities->CallLogPop(1771);
13549  return(false);
13550  }
13551  for(int x = 1; x < SignallerSpeedStr.Length() + 1; x++)
13552  {
13553  if((SignallerSpeedStr[x] < '0') || (SignallerSpeedStr[x] > '9'))
13554  {
13555  TimetableMessage(GiveMessages, "Signaller speed contains invalid characters in '" + TrainInfoStr + "'");
13556  Utilities->CallLogPop(1769);
13557  return(false);
13558  }
13559  }
13560  SignallerSpeed = SignallerSpeedStr.ToInt();
13561  if(SignallerSpeed > TTrain::MaximumSpeedLimit)
13562  {
13563  SignallerSpeed = TTrain::MaximumSpeedLimit;
13564  if(!SigSHigh)
13565  {
13566  TimetableMessage(GiveMessages, "Signaller speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13567  SigSHigh = true;
13568  }
13569  }
13570  if(SignallerSpeed < 10)
13571  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13572  {
13573  SignallerSpeed = 10;
13574  if(!SigSLow)
13575  {
13576  TimetableMessage(GiveMessages, "Signaller speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
13577  SigSLow = true;
13578  }
13579  // Utilities->CallLogPop(1770);
13580  // return false;
13581  }
13582  Utilities->CallLogPop(904);
13583  return(true);
13584 }
13585 
13586 // ---------------------------------------------------------------------------
13587 
13588 bool TTrainController::SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &NumberOfRepeats,
13589  bool GiveMessages)
13590 {
13591  // Format must be: R;mm;dd;nn mm may be 1, 2 or more digits, dd may be 1 or 2 digits, nn may be 1, 2 or more digits
13592  // function checks validity of each item and returns false for error
13593  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitRepeat," + OneEntry);
13594  if(OneEntry.Length() < 7)
13595  {
13596  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13597  Utilities->CallLogPop(865);
13598  return(false);
13599  }
13600  int SemiColonCount = 0;
13601 
13602  for(int x = 1; x < OneEntry.Length() + 1; x++)
13603  {
13604  if(OneEntry[x] == ';')
13605  {
13606  SemiColonCount++;
13607  }
13608  }
13609  if(SemiColonCount != 3)
13610  {
13611  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13612  Utilities->CallLogPop(866);
13613  return(false);
13614  }
13615  if((OneEntry[1] != 'R') || (OneEntry[2] != ';'))
13616  {
13617  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13618  Utilities->CallLogPop(867);
13619  return(false);
13620  }
13621  AnsiString Remainder = OneEntry.SubString(3, OneEntry.Length() - 2);
13622  // strip off R;
13623 
13624  int Pos = 0;
13625 
13626  Pos = Remainder.Pos(';');
13627  AnsiString MinutesStr = Remainder.SubString(1, Pos - 1);
13628 
13629  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13630  if(MinutesStr == "")
13631  {
13632  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute increment segment missing");
13633  Utilities->CallLogPop(868);
13634  return(false);
13635  }
13636  if(MinutesStr.Length() > 3)
13637  // added for v2.3.1 following Albie Vowles' reported error in repeat value 03/02/20
13638  {
13639  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute value too high, maximum value is 999");
13640  Utilities->CallLogPop(2119);
13641  return(false);
13642  }
13643  for(int x = 1; x < MinutesStr.Length() + 1; x++)
13644  {
13645  if((MinutesStr[x] < '0') || (MinutesStr[x] > '9'))
13646  {
13647  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in minute increment segment");
13648  Utilities->CallLogPop(869);
13649  return(false);
13650  }
13651  }
13652  RearStartOrRepeatMins = MinutesStr.ToInt();
13653  if(RearStartOrRepeatMins == 0)
13654  {
13655  TimetableMessage(GiveMessages, "Repeat minute increment is zero in: '" + OneEntry + "' - can't have a zero value");
13656  Utilities->CallLogPop(870);
13657  return(false);
13658  }
13659  Pos = Remainder.Pos(';');
13660  AnsiString DigitsStr = Remainder.SubString(1, Pos - 1);
13661 
13662  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13663  if(DigitsStr == "")
13664  {
13665  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - headcode increment segment missing");
13666  Utilities->CallLogPop(871);
13667  return(false);
13668  }
13669  for(int x = 1; x < DigitsStr.Length() + 1; x++)
13670  {
13671  if((DigitsStr[x] < '0') || (DigitsStr[x] > '9'))
13672  {
13673  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in headcode increment segment");
13674  Utilities->CallLogPop(872);
13675  return(false);
13676  }
13677  }
13678  if(DigitsStr.Length() > 2)
13679  {
13680  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - maximum number of digits for headcode increment is 2");
13681  Utilities->CallLogPop(873);
13682  return(false);
13683  }
13684  FrontStartOrRepeatDigits = DigitsStr.ToInt();
13685 /* allow zero digit increments so HC can stay same for repeated services - for many suburban services the headcode digits relate to the
13686  route rather than the service
13687  if(FrontStartOrRepeatDigits == 0)
13688  {
13689  TimetableMessage(GiveMessages, "Repeat headcode increment is zero in: '" + OneEntry + "' - can't have a zero value");
13690  Utilities->CallLogPop(874);
13691  return false;
13692  }
13693 */
13694  if(!Last2CharactersBothDigits(0, ServiceReference) && (FrontStartOrRepeatDigits > 0))
13695  // new for v0.6b for unrestricted headcodes
13696  {
13697  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry +
13698  "' - a repeating service with incrementing digits must have digits as its last two headcode characters");
13699  Utilities->CallLogPop(1889);
13700  return(false);
13701  }
13702  AnsiString NumberStr = Remainder;
13703 
13704  if(NumberStr == "")
13705  {
13706  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - number of repeats missing");
13707  Utilities->CallLogPop(875);
13708  return(false);
13709  }
13710  if(NumberStr.Length() > 4)
13711  // added for v2.3.1 following Albie Vowles' reported error 03/02/20
13712  {
13713  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - repeat value too high, no timetabled event can exceed 95 hours & 59 minutes");
13714  Utilities->CallLogPop(2118);
13715  return(false);
13716  }
13717  for(int x = 1; x < NumberStr.Length() + 1; x++)
13718  {
13719  if((NumberStr[x] < '0') || (NumberStr[x] > '9'))
13720  // catches negative numbers
13721  {
13722  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in number of repeats");
13723  Utilities->CallLogPop(876);
13724  return(false);
13725  }
13726  }
13727  NumberOfRepeats = NumberStr.ToInt();
13728  if(NumberOfRepeats == 0)
13729  {
13730  TimetableMessage(GiveMessages, "Number of repeats is zero in: '" + OneEntry + "' - if no repeats are needed the repeat should be omitted");
13731  Utilities->CallLogPop(877);
13732  return(false);
13733  }
13734  Utilities->CallLogPop(878);
13735  return(true);
13736 }
13737 
13738 // ---------------------------------------------------------------------------
13739 
13740 bool TTrainController::SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag) //TwoLocationFlag added at v2.9.1
13741 /* Note that here the TrainDataVector has been compiled with FinalCall true in ProcessOneTimetableLine so work on the
13742  vector rather than the timetable
13743  Note also that for unlocated Snt entries the LocationType hasn't yet been set
13744 
13745  Many of the errors caught here duplicate those in the preliminary checks, but leave in for completeness
13746 
13747 Updated significantly for v2.15.0. Current procedure:-
13748 
13749 Preliminary checks for v0.2b without changing anything, carry each out separately:-
13750  1) must have at least one actionvector entry
13751  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13752  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13753  4) first entry must be a start;
13754  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13755  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13756  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13757  5) a start must be the first entry;
13758  6) a repeat entry must be the last;
13759  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13760  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13761  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13762 
13763 Set location for located Snt or Snt-sh and ensure successor AtLocation
13764 For unlocated Snt-sh give error message
13765 For unlocated Snt & not sig control check successor moving
13766 
13767 Check all other starts (all located) have valid successors
13768 
13769 Set location for Sns-sh and Sns-fsh from following TimeLoc, if not one then give message
13770 
13771 Carry out linkage checks to ensure all links present, no data set yet & locations not checked yet. First check for duplicates, then for cross
13772 references, then for non-repeating shuttle cross refs. This is done because the later location naming functions give error messages if there
13773 are missing links.
13774 
13775 Set names for all Fns finishes from earlier named event or fail if can't find
13776 Set names for linked Sns events with same event times from above, but first carry out immediate successor checks and give error message for:-
13777 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13778 not found, that check done later.
13779 
13780 Trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set
13781 from linked fsp/rsp events)
13782 
13783 Name all fsp/rsp events, then check that all named or give error message.
13784 
13785 Set all Sfs names from above fsp/rsp links with same event times, but first carry out immediate successor checks and give error message for:-
13786 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13787 not found, that check done later.
13788 
13789 Set remaining AtLoc Command locations from preceding named event
13790 
13791 All location names should now be set
13792 
13793 Final detailed check of names for all AtLoc Commands. If find any without a name give an error message:-
13794 If jbo, fsp, rsp, cdt dsc or cms say must be preceded by a named event at same location, normally an arrival
13795 If Sns or Sfs say to make sure the linked finish event is preceded by a named event at same location, normally an arrival
13796 If Snt-sh say to make sure that the service starts with zero speed and is at a named location
13797 If Sns-fsh or Sns-sh say to make sure that the event is followed (not necessarily immediately) by a departure
13798 If Frh, Fns, Fjo, Frh-sh, Fns-sh or F-nshs say that the event must be preceded by an event at the same location that has an identified location name,
13799 normally an arrival.
13800 Missing: pas & Fer not AtLoc, Snt whether located or not covered in detail earlier.
13801 
13802 Later checks as before 2.15.0 changes:-
13803 
13804 Check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13805 
13806 Set arrival & departure times for TimeLocs & set their EventTimes to -1 (up to now all have times as EventTime)
13807 
13808 Perform remaining successor checks for TimeLocs
13809 
13810 Check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1, & repeats have no times
13811 set
13812 
13813 Check times stay same or increase through a service, note that can have time of 0 if include midnight
13814 
13815 Check locations consistent
13816 
13817 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0) i.e.
13818 same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0. Message
13819 given in InterfaceUnit
13820 
13821 Check all locations except unlocated 'Snt' & 'Fer' have LocationName set and throw error if not.
13822 
13823 Carry out full cross reference and duplicate link checks for all services inc shuttles, and set data and check location consistency
13824 
13825 Check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
13826 when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles, don't ever need to and as
13827 designed would skip repeats
13828 
13829 Check all entries have all types set to something and throw error if not
13830 
13831 All OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
13832 
13833 Check that don't include any Continuation names
13834 
13835 Check that all repeat times below 96h
13836 
13837 Now that all set up change any extended headcodes back to ordinary headcodes (had been service references until now.
13838 
13839 Finally call BuildContinuationTrainExpectationMultiMap
13840 
13841 ***********************************
13842 
13843 For info:-
13844 class TActionVectorEntry //contains a single train action - repeat entry is also of this class though no train action is taken for it
13845 {
13846 public:
13847 AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode; ///< string values for timetabled event entries, null
13849 bool SignallerControl; ///< indicates a train that is defined by the timetable as under signaller control
13850 bool Warning; ///< if set triggers an alert in the warning panel when the action is reached
13851 int NumberOfRepeats; ///< the number of repeating services
13852 int RearStartOrRepeatMins, FrontStartOrRepeatDigits; ///< dual-purpose variables used for the TrackVectorPositions of the rear and front
13854 TDateTime EventTime, ArrivalTime, DepartureTime; ///< relevant times at which the action is timetabled, zeroed on creation so change
13856 TNumList ExitList; ///< the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
13857 TTimetableFormatType FormatType; ///< defines the timetable action type
13858 TTimetableLocationType LocationType; ///< indicates where the train is when the relevant action occurs
13859 TTimetableSequenceType SequenceType; ///< indicates where in the sequence of codes the action lies
13860 TTimetableShuttleLinkType ShuttleLinkType; ///< indicates whether or not the action relates to a shuttle service link
13861 TTrainDataEntry *LinkedTrainEntryPtr; ///< link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle
13863 TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr; ///< pointer used by shuttles for the non-shuttle train links, in & out, the
13865 
13866 // inline function
13867 
13869 TActionVectorEntry() {
13870 RearStartOrRepeatMins=0; FrontStartOrRepeatDigits=0; NumberOfRepeats=0; FormatType=NoFormat;
13871 SequenceType=NoSequence; LocationType=NoLocation; ShuttleLinkType=NoShuttleLink, EventTime=TDateTime(-1);
13872 ArrivalTime=TDateTime(-1); DepartureTime=TDateTime(-1); LinkedTrainEntryPtr=0; NonRepeatingShuttleLinkEntryPtr=0;
13873 Warning = false; SignallerControl = false;
13874 }
13875 };
13876 
13877 typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
13878 
13879 class TTrainDataEntry //contains all data for a single train - copied into train object when becomes active
13880 {
13881 public:
13882 AnsiString HeadCode, ServiceReference, Description; ///< headcode is the first train's headcode, rest are calculated from repeat
13885 double MaxBrakeRate; ///< in metres/sec/sec
13886 double MaxRunningSpeed; ///< in km/h
13887 double PowerAtRail; ///< in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
13888 int Mass; ///< in kg
13889 int NumberOfTrains; ///< number of repeats + 1
13890 int SignallerSpeed; ///< in km/h for use when under signaller control
13891 int StartSpeed; ///< in km/h
13892 TActionVector ActionVector; ///< all the actions for the train
13893 TTrainOperatingDataVector TrainOperatingDataVector; ///< operating information for the train including all its repeats
13894 
13895 //inline function
13896 
13898 TTrainDataEntry()
13899 {
13900 StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;
13901 }
13902 };
13903 
13904 Allowable successors:-
13905 Snt unlocated -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas; No others
13906 Snt located -> No starts, no finishes except Frh, Fjo (as of v2.0.0), Fns, and F-nshs, no repeat, pas, TimeTimeLoc or TimeLoc arr;
13907 any other cmd or TimeLoc (dep) OK
13908 Snt-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK
13909 Sfs -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc, TimeLoc arr, rsp, fsp; any other cmd or
13910 TimeLoc (dep) OK [must have departure & arrival before another split]
13911 Sns -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc or TimeLoc arr; any other cmd or TimeLoc (dep) OK
13912 Sns-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13913 sequence to
13914 set location, else fails)
13915 Sns-fsh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13916 sequence to
13917 set location, else fails)
13918 Fns -> R only [must be preceded by a TimeLoc arrival at the finish location, not necessarily immediately]
13919 F-nshs -> Nothing (no repeats permitted)
13920 Fjo -> R only
13921 Frh -> R only
13922 Fer -> R only
13923 Frh-sh -> R only
13924 Fns-sh -> R only
13925 jbo -> No starts, repeats, pas, Fer or TimeTimeLoc; TimeLoc (dep), others OK [must be preceded by an event whose location is set]
13926 fsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13927 rsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13928 cdt -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13929 dsc -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13930 cms -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13931 TimeLoc (arr) -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK
13932 TimeLoc (dep) -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13933 TimeTimeLoc -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13934 (new) pas -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13935 Repeat -> Nothing
13936 
13937 There must be a TimeLoc arrival (or a Sns start at location) in a sequence so successive cmd locations can be set
13938 Check all Snt's & set Locations if located (located = zero start speed, either element at a location (but if rear element
13939 is a continuation then treated as unlocated), and location listed in the next TimeLoc entry, though needn't be immediately after)
13940 If Snt entry at a location specified in a following TimeLoc entry but start speed > 0 give error message
13941 Check all times increase or stay same through ActionVector
13942 Cycle through all entries in vector setting arr & dep times based on above list
13943 Add locations to all relevant cmd entries based on earlier arrival location (or earlier reference for Sfs & Sns)
13944 Check locations match the arr & dep TimeLoc entries
13945 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs
13946 Make above valid succession checks
13947 Check all splits have matching Sfs headcodes (both ways), add locations to Sfs's & check times same [Sfs loc derived from preceding fsp/rsp
13948 loc]
13949 Check all new service headcodes (Sns) have matching headcodes (both ways), add locations to Sns's & check times same [Sns loc derived from
13950 preceding Fns loc]
13951 Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
13952 Check each Fns has matching Sns headcodes (both ways), add locations to Fns's & check times same
13953 Check all joins have matching headcodes (both ways), locations & times & don't occur in same sequence
13954 Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
13955 Set train info for Sfs & Sns entries
13956 Check each repeat entry exactly matches any included joins or splits (user has to enter it to show that really wants it)
13957 Check at least one platform long enough for a split (only need 2 lengths) & disallow if not, need length of 2 & 1 extra
13958 element at each end, or length of 3 & 1 extra element at either end
13959 Check all TimeLocs have either Arr or Dep times set and EventTime == -1
13960 Check all Cmds have EventTime set & Arr & Dep times = -1
13961 Check all locations except unlocated Snts, Fers and Repeats have a LocationName
13962 
13963 Give messages in function if errors detected and clear the vector. Return false for failure.
13964 */
13965 
13966 /* Earlier checks:-
13967 Checks carried out with error messages in this function:-
13968 At least one comma in the line (it's based on a csv file);
13969 No entries following train information;
13970 At least one comma in remainder after train information (i.e at least a start and a finish entry);
13971 SplitEntry returns false in an intermediate entry - message repeats the entry for information;
13972 First entry not a start entry;
13973 Train information incomplete before a start entry;
13974 Entry follows a finish entry but doesn't begin with 'R';
13975 SplitEntry returns false in a finish entry - message repeats the entry for information;
13976 Last action entry isn't a finish entry.
13977 
13978 Function returns false with no message if:-
13979 Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
13980 time is found at all then an error message is given in the calling function);
13981 SplitTrainInfo returns false (message given in called function);
13982 SplitRepeat returns false (message given in called function).
13983 
13984 Double crosslink (shuttle) table: [OtherHeadCode, NonRepeatingShuttleLinkHeadCode, LinkedTrainEntryPtr, NonRepeatingShuttleLinkEntryPtr] <-- these for
13985 easier searching for this table
13986 
13987 Command Format OtherHead NonRepeating- LinkedTrain- NonRepeating- Decsription
13988  Code ShuttleLink- EntryPtr ShuttleLink
13989  HeadCode EntryPtr
13990 
13991 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
13992 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
13993 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
13994 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (fdr-shld be rtn)N (shld be fdr) Luckily NonRep link not needed
13995 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
13996 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
13997 
13998 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
13999 
14000 */
14001 {
14002  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SecondPassActions,");
14003  TDateTime LastArrivalTime;
14004  double MinDwellTime;
14005  if(TrainDataVector.empty())
14006  {
14007  SecondPassMessage(GiveMessages, "Error in timetable - there appear to be no train services in the timetable, it must contain at least one");
14008  TrainDataVector.clear();
14009  Utilities->CallLogPop(1832);
14010  return(false);
14011  }
14012 /* new preliminary checks for v0.2b without changing anything, carry each out separately:-
14013  1) must have at least one actionvector entry
14014  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
14015  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
14016  4) first entry must be a start;
14017  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
14018  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
14019  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
14020  5) a start must be the first entry;
14021  6) a repeat entry must be the last;
14022  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
14023  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
14024  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
14025 */
14026 
14027  TwoLocationList.clear(); //empty the list to begin with, added at v2.9.1
14028  TwoLocationFlag = false; //added at v2.9.1
14029  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (1)
14030  {
14031  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14032  if(TrainDataVector.at(x).ActionVector.empty())
14033  {
14034  SecondPassMessage(GiveMessages, "Error in timetable - the following service has no listed events, there must be at least one: " + TDEntry.HeadCode);
14035  TrainDataVector.clear();
14036  Utilities->CallLogPop(1833);
14037  return(false);
14038  }
14039  }
14040  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (2)
14041  {
14042  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14043  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14044  if(!(AVEntry0.SignallerControl))
14045  {
14046  if(TrainDataVector.at(x).ActionVector.size() == 1)
14047  {
14048  SecondPassMessage(GiveMessages, "Error in timetable - service must have a start event and at least one other for: " + TDEntry.HeadCode);
14049  TrainDataVector.clear();
14050  Utilities->CallLogPop(1822);
14051  return(false);
14052  }
14053  }
14054  }
14055  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (3)
14056  {
14057  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14058  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14059  if(AVEntry0.SignallerControl)
14060  {
14061  if(TrainDataVector.at(x).ActionVector.size() > 2)
14062  {
14063  SecondPassMessage(GiveMessages,
14064  "Error in timetable - a signaller controlled service can have no more than one item (a repeat) after the start event, see: " +
14065  TDEntry.HeadCode);
14066  TrainDataVector.clear();
14067  Utilities->CallLogPop(1837);
14068  return(false);
14069  }
14070  if(TrainDataVector.at(x).ActionVector.size() > 1)
14071  {
14072  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14073  if(AVEntry1.FormatType != Repeat)
14074  {
14075  SecondPassMessage(GiveMessages,
14076  "Error in timetable - a signaller controlled service cannot have any other than a repeat after the start event, see: " + TDEntry.HeadCode);
14077  TrainDataVector.clear();
14078  Utilities->CallLogPop(1838);
14079  return(false);
14080  }
14081  }
14082  }
14083  }
14084  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (4)
14085  {
14086  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14087  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14088  if(AVEntry0.SequenceType != StartSequence)
14089  {
14090  SecondPassMessage(GiveMessages, "Error in timetable - the first event must be a start for: " + TDEntry.HeadCode);
14091  TrainDataVector.clear();
14092  Utilities->CallLogPop(1824);
14093  return(false);
14094  }
14095  if((AVEntry0.Command == "Snt") && !AVEntry0.SignallerControl) // (4a) sig control condition added so there is a second AVEntry
14096  // 4a added at v2.0.0. This is only a rough check, Fer only valid for an unlocated Snt
14097  // and others for a located Snt, but those checks done later
14098  {
14099  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1); // must be a second entry if first not signallercontrol
14100  if((AVEntry1.SequenceType == FinishSequence) && ((AVEntry1.Command == "Fns-sh") || (AVEntry1.Command == "Frh-sh")))
14101  {
14102  SecondPassMessage(GiveMessages, "Error in timetable - finish events Fns-sh and Frh-sh are not permitted immediately after an Snt entry for: " +
14103  TDEntry.HeadCode); //these are the only AtLoc finishes not allowed
14104  TrainDataVector.clear();
14105  Utilities->CallLogPop(2046);
14106  return(false);
14107  }
14108  }
14109  if((AVEntry0.Command == "Sns") || (AVEntry0.Command == "Sfs")) // (4b)
14110  // 4b added at v2.15.0
14111  {
14112  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14113  if((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo"))
14114  {
14115  SecondPassMessage(GiveMessages, "Error in timetable - only 'Frh' or 'Fjo' finish events are permitted immediately after "
14116  "an 'Sns' or 'Sfs' event for: " + TDEntry.HeadCode + ". The program is unable to determine the "
14117  "location of any other type of finish.");
14118  TrainDataVector.clear();
14119  Utilities->CallLogPop(2580);
14120  return(false);
14121  }
14122  }
14123  if((AVEntry0.Command == "Snt-sh") || (AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh")) // (4c)
14124  // 4c added at v2.15.0
14125  {
14126  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14127  if(AVEntry1.SequenceType == FinishSequence)
14128  {
14129  SecondPassMessage(GiveMessages, "Error in timetable - a finish event can't immediately follow an 'Snt-sh', 'Sns-sh' or 'Sns-fsh' "
14130  "event for: " + TDEntry.HeadCode);
14131  TrainDataVector.clear();
14132  Utilities->CallLogPop(2616);
14133  return(false);
14134  }
14135  }
14136  }
14137  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (5)
14138  {
14139  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14140  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14141  {
14142  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14143  if((AVEntry.SequenceType == StartSequence) && (y != 0))
14144  {
14145  SecondPassMessage(GiveMessages, "Error in timetable - a start event is present that is not the first event for: " + TDEntry.HeadCode);
14146  TrainDataVector.clear();
14147  Utilities->CallLogPop(1825);
14148  return(false);
14149  }
14150  }
14151  }
14152  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (6)
14153  {
14154  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14155  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14156  {
14157  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14158  if((AVEntry.FormatType == Repeat) && (y != (TrainDataVector.at(x).ActionVector.size() - 1)))
14159  {
14160  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is present that is not the last item for: " + TDEntry.HeadCode);
14161  TrainDataVector.clear();
14162  Utilities->CallLogPop(1826);
14163  return(false);
14164  }
14165  }
14166  }
14167  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (7)
14168  {
14169  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14170  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14171  {
14172  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14173  if((y == 0) && AVEntry.SignallerControl)
14174  {
14175  break;
14176  }
14177  if(y == (TrainDataVector.at(x).ActionVector.size() - 1))
14178  {
14179  if((AVEntry.FormatType != Repeat) && (AVEntry.SequenceType != FinishSequence))
14180  {
14181  SecondPassMessage(GiveMessages, "Error in timetable - the last item must be either a finish event or a repeat for: " + TDEntry.HeadCode);
14182  TrainDataVector.clear();
14183  Utilities->CallLogPop(1827);
14184  return(false);
14185  }
14186  if(AVEntry.FormatType == Repeat)
14187  {
14188  const TActionVectorEntry &LastButOneAVEntry = TrainDataVector.at(x).ActionVector.at(y - 1);
14189  if(LastButOneAVEntry.SequenceType != FinishSequence)
14190  {
14191  SecondPassMessage(GiveMessages, "Error in timetable - the event immediately before the repeat must be a finish for: " + TDEntry.HeadCode);
14192  TrainDataVector.clear();
14193  Utilities->CallLogPop(1828);
14194  return(false);
14195  }
14196  }
14197  }
14198  }
14199  }
14200  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (8)
14201  {
14202  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14203  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14204  {
14205  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14206  if(AVEntry.SequenceType == FinishSequence)
14207  {
14208  if((y != (TrainDataVector.at(x).ActionVector.size() - 1)) && (y != (TrainDataVector.at(x).ActionVector.size() - 2)))
14209  {
14210  SecondPassMessage(GiveMessages, "Error in timetable - a finish event must be either the last or last but one for: " + TDEntry.HeadCode);
14211  TrainDataVector.clear();
14212  Utilities->CallLogPop(1829);
14213  return(false);
14214  }
14215  if(y == (TrainDataVector.at(x).ActionVector.size() - 2))
14216  {
14217  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
14218  {
14219  SecondPassMessage(GiveMessages, "Error in timetable - the only event that can follow a finish event is a repeat for: " + TDEntry.HeadCode);
14220  TrainDataVector.clear();
14221  Utilities->CallLogPop(1830);
14222  return(false);
14223  }
14224  }
14225  }
14226  }
14227  }
14228 
14229  // end of new preliminary checks
14230 
14231  // check start event successor validity
14232  // For Snt & Snt-sh set location if stopped, don't set any times yet
14233  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14234  {
14235  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14236  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14237  // use reference so can change internals where necessary
14238  if((AVEntry0.Command == "Snt") || (AVEntry0.Command == "Snt-sh"))
14239  {
14240  AnsiString LocationName = "";
14241  if(IsSNTEntryLocated(0, TDEntry, LocationName))
14242  // it is at a location
14243  {
14244  AVEntry0.LocationName = LocationName; //located Snt location name set
14245  AVEntry0.LocationType = AtLocation;
14246  // check successor validity for located Snt that isn't a SignallerControl entry
14247  if(!AVEntry0.SignallerControl)
14248  {
14249  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14250  // at least 2 entries present checked in integrity check so (1) valid
14251  if(!AtLocSuccessor(AVEntry1))
14252  {
14253  // Frh following Snt-sh will return false in location check, so no need to check here
14254  SecondPassMessage(GiveMessages, "Error in timetable - stopped 'Snt' or 'Snt-sh' event (stationary train) followed by event '"
14255  + AVEntry1.OneLineText + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14256  TrainDataVector.clear();
14257  Utilities->CallLogPop(523);
14258  return(false);
14259  }
14260  }
14261  }
14262  else // check not Snt-sh & carry out successor validity checks for unlocated Snt that isn't a SignallerControl entry
14263  {
14264  if(AVEntry0.Command == "Snt-sh")
14265  {
14266  SecondPassMessage(GiveMessages, "Error in timetable - 'Snt-sh' event not at stop location for: " + TDEntry.HeadCode);
14267  TrainDataVector.clear();
14268  Utilities->CallLogPop(1042);
14269  return(false);
14270  }
14271  AVEntry0.LocationType = EnRoute;
14272  if(!AVEntry0.SignallerControl)
14273  {
14274  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14275  // at least 2 entries checked in integrity check so (1) valid
14276  if(!MovingSuccessor(AVEntry1))
14277  {
14278  SecondPassMessage(GiveMessages, "Error in timetable - an 'Snt' command for a moving train is not followed by 'Fer', 'pas' or an arrival for: " +
14279  TDEntry.HeadCode);
14280  TrainDataVector.clear();
14281  Utilities->CallLogPop(790);
14282  return(false);
14283  }
14284  }
14285  }
14286  }
14287  // check other start successors, all AtLoc
14288  else if(AVEntry0.SequenceType == StartSequence)
14289  {
14290  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14291  // at least 2 entries present checked in integrity check so (1) valid
14292  if(!AtLocSuccessor(AVEntry1))
14293  {
14294  SecondPassMessage(GiveMessages, "Error in timetable - 'Sfs', 'Sns', 'Sns-sh', 'Snt-fsh' or 'Sns-fsh' (stationary train) followed by event '"
14295  + AVEntry1.OneLineText + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14296  TrainDataVector.clear();
14297  Utilities->CallLogPop(793);
14298  return(false);
14299  }
14300  }
14301  }
14302 
14303  // set Sns-sh & Sns-fsh locations same as following TimeLoc departure entry location, if no departure before end of sequence give error message
14304  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
14305  {
14306  bool FoundFlag = false;
14307  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14308  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14309  // use reference so can change internals
14310  if((AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh"))
14311  {
14312  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
14313  {
14314  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y);
14315  if(AVEntry2.FormatType == TimeLoc)
14316  {
14317  FoundFlag = true;
14318  AVEntry0.LocationName = AVEntry2.LocationName; //Sns-sh & Sns-fsh location names set
14319  break;
14320  }
14321  }
14322  if(!FoundFlag)
14323  {
14324  SecondPassMessage(GiveMessages, "Error in timetable - no location departure following an 'Sns-sh' or 'Sns-fsh' event for: " + TDEntry.HeadCode);
14325  TrainDataVector.clear();
14326  Utilities->CallLogPop(851);
14327  return(false);
14328  }
14329  }
14330  }
14331 
14332 //carry out preliminary check on service ref linkages without setting any data - added at v2.15.0 as can be location errors if linked trains not present
14333 //first check for duplicates then linkages (also checked later but leave that in)
14334  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14335  {
14336  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14337  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14338  {
14339  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14340  if(AVEntry.OtherHeadCode != "")
14341  {
14342  if(!CheckForDuplicateCrossReferences(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
14343  {
14344  Utilities->CallLogPop(2610);
14345  return(false); // error message given in called function
14346  }
14347  }
14348  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14349  {
14350  if(!CheckForDuplicateCrossReferences(3, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
14351  {
14352  Utilities->CallLogPop(2611);
14353  return(false); // error message given in called function
14354  }
14355  }
14356  }
14357  }
14358 //cross reference check
14359  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14360  {
14361  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14362  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14363  {
14364  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14365  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
14366  {
14367  if(AVEntry.OtherHeadCode != "")
14368  {
14369  if(!CheckCrossReferencesAndSetData(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, false, GiveMessages))
14370  // false = non-shuttle
14371  {
14372  Utilities->CallLogPop(2612);
14373  return(false); // error message given in called function
14374  }
14375  }
14376  }
14377  }
14378  }
14379 
14380 // now repeat the check just for the shuttles
14381  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14382  {
14383  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14384  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14385  {
14386  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14387  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
14388  {
14389  if(AVEntry.OtherHeadCode != "")
14390  {
14391  if(!CheckCrossReferencesAndSetData(3, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, false, GiveMessages))
14392  // true = shuttle
14393  {
14394  Utilities->CallLogPop(2613);
14395  return(false); // error message given in called function
14396  }
14397  }
14398  }
14399  }
14400  }
14401 
14402 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
14403  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14404  {
14405  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14406  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14407  {
14408  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14409  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14410  {
14411  if(!CheckNonRepeatingShuttleLinksAndSetData(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, false, GiveMessages))
14412  {
14413  Utilities->CallLogPop(2614);
14414  return(false); // error message given in called function
14415  }
14416  }
14417  }
14418  }
14419 
14420 //at v2.15.0 we want to set Sns, Sfs location names, but to set Sns & Sfs first need the linked Fns & fsp/rsp to have locations set as they aren't yet,
14421 //and before v2.15.0 they were set from the corresponding Sns & Sfs locations, which in turn were set from later TimeLoc departures. At v2.15.0 it
14422 //is required to have these commands followed by Frh & Fjo, so this is why we need the linked Fns & fsp/rsp to have locations set first. Now all Fns
14423 //will have a TimeLoc before, so that can provide its location, but fsp/rsp? Must they have a TimeLoc before? No, and can't rely on starting Sfs
14424 //having the location set yet. So, new restriction, insist on an rsp/fsp having a TimeLoc before it or a located Snt, and use one of those to set the
14425 //location for the rsp/fsp and hence the linked Sfs.
14426 
14427 //NB can't allow an Sfs to be followed by another split or won't find a name, test with many existing tts then add an error to find it
14428 //Fns must be preceded by an arrival
14429 
14430 //set name for Fns from earlier location name or fail if can't find
14431  bool LocFoundFlag, FnsFoundFlag;
14432  TActionVectorEntry *AVEntryFns;
14433  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14434  {
14435  LocFoundFlag = false;
14436  FnsFoundFlag = false;
14437  for(int y = TrainDataVector.at(x).ActionVector.size() - 1; y >= 0; y--) //search backwards
14438  {
14439  if(TrainDataVector.at(x).ActionVector.at(y).Command == "Fns")
14440  {
14441  AVEntryFns = &TrainDataVector.at(x).ActionVector.at(y);
14442  FnsFoundFlag = true;
14443  continue;
14444  }
14445  if(!FnsFoundFlag)
14446  {
14447  continue;
14448  }
14449  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "")
14450  {
14451  LocFoundFlag = true;
14452  AVEntryFns->LocationName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
14453 // double EVT = double(AVEntryFns->EventTime); //test
14454  break; //name found
14455  }
14456  if(TrainDataVector.at(x).ActionVector.at(y).LocationType == AtLocation) //not named yet
14457  {
14458  continue;
14459  }
14460  else
14461  {
14462  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14463  "by an event at the same location that has an identified location name, normally an arrival, see "
14464  + TrainDataVector.at(x).ServiceReference);
14465  TrainDataVector.clear();
14466  Utilities->CallLogPop(2596);
14467  return(false);
14468  }
14469  }
14470  if(FnsFoundFlag && !LocFoundFlag)
14471  {
14472  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14473  "by an event at the same location that has an identified location name, normally an arrival, see "
14474  + TrainDataVector.at(x).ServiceReference);
14475  TrainDataVector.clear();
14476  Utilities->CallLogPop(2597);
14477  return(false);
14478  }
14479  }
14480 
14481 //now set all names for Sns entries from the above, new at v2.15.0
14482  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14483  {
14484  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14485  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14486  // use reference so can change internals
14487  if(AVEntry0.Command == "Sns")
14488  {
14489  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called).
14490  //This isn't rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14491  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14492  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until
14493  //StripExcessFromHeadCode called at end of this function
14494  //need to be the same: forward & reverse service refs, event times, commands correspond
14495 
14496  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14497  if(TDEntry.ActionVector.size() < 2)
14498  {
14499  SecondPassMessage(GiveMessages, "Error in timetable - insufficient events follwing an 'Sns' event for: " + TDEntry.HeadCode);
14500  TrainDataVector.clear();
14501  Utilities->CallLogPop(2598);
14502  return(false);
14503  }
14504  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14505  if(!AtLocSuccessor(AVEntry1))
14506  {
14507  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event (stationary train) is followed by event '" + AVEntry1.OneLineText
14508  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14509  TrainDataVector.clear();
14510  Utilities->CallLogPop(2599);
14511  return(false);
14512  }
14513  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14514  (AVEntry1.FormatType == Repeat))
14515  {
14516  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event (stationary train) is followed by event '" + AVEntry1.OneLineText + "' (moving train) for: "
14517  + TDEntry.HeadCode + ". At least one is incorrect.");
14518  TrainDataVector.clear();
14519  Utilities->CallLogPop(2600);
14520  return(false);
14521  }
14522 
14523  //now set the location and location type
14524  TDateTime SnsEventTime = AVEntry0.EventTime;
14525 // double EVT = double(SnsEventTime); //test
14526  AnsiString SnsServiceRef = TDEntry.ServiceReference;
14527  bool BreakFlag = false;
14528  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14529  {
14530  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14531  {
14532  if((TrainDataVector.at(y).ActionVector.at(z).Command == "Fns") && (SnsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) &&
14533  (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14534  { //forward linkage found
14535  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SnsServiceRef) //OtherHeadCode values are service refs, see above
14536  { //reverse linkage found
14537  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14538  AVEntry0.LocationType = AtLocation;
14539  BreakFlag = true;
14540  break;
14541  }
14542  }
14543  }
14544  if(BreakFlag)
14545  {
14546  break;
14547  }
14548  }
14549  //test for any unnamed AtLoc entries at end of name setting
14550  }
14551  }
14552 
14553 //trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set from linked fsp/rsp events)
14554  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14555  {
14556  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14557  if(AVEntry0.Command == "Sfs")
14558  {
14559  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
14560  {
14561  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "") //must be a timeloc as only they have loc set and are AtLoc (non-AtLoc trapped above)
14562  {
14563  break;
14564  }
14565  else if((TrainDataVector.at(x).ActionVector.at(y).Command == "fsp") || (TrainDataVector.at(x).ActionVector.at(y).Command == "rsp"))
14566  {
14567  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event must be followed by a departure and arrival before another split, see " + TrainDataVector.at(x).ServiceReference);
14568  TrainDataVector.clear();
14569  Utilities->CallLogPop(2586);
14570  return(false);
14571  }
14572  }
14573  }
14574  }
14575 
14576 //now name fsp/rsp actions
14577  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14578  {
14579  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14580  {
14581  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14582  if(AVEntry.LocationName != "")
14583  {
14584  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14585  {
14586  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14587  // use reference so can change internals where necessary
14588  if((AVEntry2.Command == "fsp") || (AVEntry2.Command == "rsp"))
14589  {
14590  AVEntry2.LocationName = AVEntry.LocationName;
14591  } //test for any unnamed AtLoc entries at end of name setting
14592  else if(AVEntry2.LocationType != AtLocation)
14593  {
14594  break;
14595  }
14596  }
14597  }
14598  }
14599  }
14600 
14601 //check that all named or give error message
14602  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14603  {
14604  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14605  {
14606  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14607  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14608  {
14609  if(AVEntry.LocationName == "")
14610  {
14611  SecondPassMessage(GiveMessages, "Error in timetable - an 'fsp' or 'rsp' event must be preceded by an event at the same location that has an identified location name, normally an arrival, see "
14612  + TrainDataVector.at(x).ServiceReference);
14613  TrainDataVector.clear();
14614  Utilities->CallLogPop(2617);
14615  return(false);
14616  }
14617  }
14618  }
14619  }
14620 
14621 //now set all Sfs entries from the above
14622  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
14623  {
14624  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14625  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14626  // use reference so can change internals
14627  if(AVEntry0.Command == "Sfs")
14628  {
14629  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called). This isn't
14630  //rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14631  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14632  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until StripExcessFromHeadCode called
14633  //at end of this function
14634  //need to be the same: forward & reverse service refs, event times, commands correspond
14635 
14636  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14637  if(TDEntry.ActionVector.size() < 2)
14638  {
14639  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sfs' event for: " + TDEntry.HeadCode);
14640  TrainDataVector.clear();
14641  Utilities->CallLogPop(2587);
14642  return(false);
14643  }
14644  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14645  if(!AtLocSuccessor(AVEntry1))
14646  {
14647  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event (stationary train) is followed by event '" + AVEntry1.OneLineText +
14648  "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14649  TrainDataVector.clear();
14650  Utilities->CallLogPop(2588);
14651  return(false);
14652  }
14653  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14654  (AVEntry1.FormatType == Repeat))
14655  {
14656  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by illegal event '" + AVEntry1.OneLineText + "' for: "
14657  + TDEntry.HeadCode);
14658  TrainDataVector.clear();
14659  Utilities->CallLogPop(2589);
14660  return(false);
14661  }
14662 
14663  //now set the location and location type
14664  TDateTime SfsEventTime = AVEntry0.EventTime;
14665  AnsiString SfsServiceRef = TDEntry.ServiceReference;
14666  bool BreakFlag = false;
14667  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14668  {
14669  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14670  {
14671  if(((TrainDataVector.at(y).ActionVector.at(z).Command == "fsp") || (TrainDataVector.at(y).ActionVector.at(z).Command == "rsp")) &&
14672  (SfsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) && (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14673  { //forward linkage found
14674  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SfsServiceRef) //OtherHeadCode values are service refs, see above
14675  { //reverse linkage found
14676  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14677  AVEntry0.LocationType = AtLocation;
14678  BreakFlag = true;
14679  break;
14680  }
14681  }
14682  }
14683  if(BreakFlag)
14684  {
14685  break;
14686  }
14687  } //test for any unnamed AtLoc entries at end of name setting
14688  }
14689  }
14690 
14691  // set all cmd locations based on earlier location name in TimeLoc arrival or Sfs/Sns/Sns-sh/Sns-fsh/located Snt/Snt-sh locations
14692  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14693  {
14694  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14695  {
14696  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14697  if(AVEntry.LocationName != "")
14698  {
14699  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14700  {
14701  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14702  // use reference so can change internals where necessary
14703  if((AVEntry2.Command != "") && (AVEntry2.LocationType == AtLocation))
14704  {
14705  AVEntry2.LocationName = AVEntry.LocationName;
14706  } //test for any unnamed AtLoc entries at end of name setting
14707  else
14708  {
14709  break;
14710  }
14711  }
14712  }
14713  }
14714  }
14715  // all location names should now be set
14716 
14717 //now test for any unnamed AtLoc entries where Command != "" and give message if find any - shouldn't find any if above checks comprehensive
14718  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14719  {
14720  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14721  {
14722  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14723  if((AVEntry.LocationType == AtLocation) && (AVEntry.LocationName == "") && (AVEntry.Command != ""))
14724  {
14725  if((AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms"))
14726  {
14727  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "' must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14728  TrainDataVector.clear();
14729  Utilities->CallLogPop(2619);
14730  return(false);
14731  }
14732  else if((AVEntry.Command == "Sns") || (AVEntry.Command == "Sfs"))
14733  {
14734  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14735  "Please make sure that the finish event of the service that links to this event is preceded by an "
14736  "event at the same location that has an identified location name, normally an arrival.");
14737  TrainDataVector.clear();
14738  Utilities->CallLogPop(2620);
14739  return(false);
14740  }
14741  else if(AVEntry.Command == "Snt-sh")
14742  {
14743  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14744  "Please make sure that the service starts with zero speed and is at a named location.");
14745  TrainDataVector.clear();
14746  Utilities->CallLogPop(2623);
14747  return(false);
14748  }
14749  else if((AVEntry.Command == "Sns-fsh") || (AVEntry.Command == "Sns-sh"))
14750  {
14751  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14752  "Please make sure that the event is followed (not necessarily immediately) by a departure.");
14753  TrainDataVector.clear();
14754  Utilities->CallLogPop(2622);
14755  return(false);
14756  }
14757  else if((AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"))
14758  {
14759  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14760  TrainDataVector.clear();
14761  Utilities->CallLogPop(2621);
14762  return(false);
14763  }
14764  }
14765  }
14766  }
14767 
14768  // check remaining successor validity except for TimeLoc arr & dep since those times not set yet
14769  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14770  {
14771  bool DwellTimeWarningGiven = false;
14772  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14773  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14774  {
14775  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14776  if((AVEntry.SequenceType == FinishSequence) && (AVEntry.Command != "F-nshs"))
14777  {
14778  if(y < (TrainDataVector.at(x).ActionVector.size() - 1))
14779  // i.e at least one more, must be a repeat
14780  {
14781  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
14782  {
14783  SecondPassMessage(GiveMessages, "Error in timetable - only a repeat can follow a finish event for: " + TDEntry.HeadCode);
14784  TrainDataVector.clear();
14785  Utilities->CallLogPop(798);
14786  return(false);
14787  }
14788  }
14789  }
14790  if(AVEntry.Command == "F-nshs")
14791  {
14792  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14793  // i.e has to be the last
14794  {
14795  SecondPassMessage(GiveMessages, "Error in timetable - F-nshs (shuttle link) must be the last event for: " + TDEntry.HeadCode);
14796  TrainDataVector.clear();
14797  Utilities->CallLogPop(1049);
14798  return(false);
14799  }
14800  }
14801  if(AVEntry.Command == "pas")
14802  {
14803  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14804  {
14805  SecondPassMessage(GiveMessages, "Error in timetable - a 'pas' can't be the last event for: " + TDEntry.HeadCode);
14806  TrainDataVector.clear();
14807  Utilities->CallLogPop(1518);
14808  return(false);
14809  }
14810  }
14811  if(AVEntry.Command == "jbo")
14812  {
14813  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14814  {
14815  SecondPassMessage(GiveMessages, "Error in timetable - a 'jbo' can't be the last event for: " + TDEntry.HeadCode);
14816  TrainDataVector.clear();
14817  Utilities->CallLogPop(800);
14818  return(false);
14819  }
14820  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14821  if(!AtLocSuccessor(AVEntry2))
14822  {
14823  SecondPassMessage(GiveMessages, "Error in timetable - a jbo event (stationary train) is followed by event '" + AVEntry2.OneLineText
14824  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14825  TrainDataVector.clear();
14826  Utilities->CallLogPop(801);
14827  return(false);
14828  }
14829  }
14830  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14831  {
14832  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14833  {
14834  SecondPassMessage(GiveMessages, "Error in timetable - a train split can't be the last event for: "
14835  + TDEntry.HeadCode + ".");
14836  TrainDataVector.clear();
14837  Utilities->CallLogPop(802);
14838  return(false);
14839  }
14840  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14841  if(!AtLocSuccessor(AVEntry2))
14842  {
14843  SecondPassMessage(GiveMessages, "Error in timetable - a train split event (stationary trains) is followed by event '" + AVEntry2.OneLineText
14844  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14845  TrainDataVector.clear();
14846  Utilities->CallLogPop(803);
14847  return(false);
14848  }
14849  }
14850  if(AVEntry.Command == "cdt")
14851  {
14852  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14853  {
14854  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' can't be the last event for: " + TDEntry.HeadCode);
14855  TrainDataVector.clear();
14856  Utilities->CallLogPop(804);
14857  return(false);
14858  }
14859  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14860  if(!AtLocSuccessor(AVEntry2))
14861  {
14862  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' event (stationary train) is followed by event '" + AVEntry2.OneLineText
14863  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14864  TrainDataVector.clear();
14865  Utilities->CallLogPop(805);
14866  return(false);
14867  }
14868  }
14869  if(AVEntry.Command == "dsc")
14870  {
14871  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14872  {
14873  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' can't be the last event for: " + TDEntry.HeadCode);
14874  TrainDataVector.clear();
14875  Utilities->CallLogPop(2602);
14876  return(false);
14877  }
14878  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14879  if(!AtLocSuccessor(AVEntry2))
14880  {
14881  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' event (stationary train) is followed by event '" + AVEntry2.OneLineText
14882  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14883  TrainDataVector.clear();
14884  Utilities->CallLogPop(2603);
14885  return(false);
14886  }
14887  }
14888  if(AVEntry.Command == "cms")
14889  {
14890  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14891  {
14892  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' can't be the last event for: " + TDEntry.HeadCode);
14893  TrainDataVector.clear();
14894  Utilities->CallLogPop(2711);
14895  return(false);
14896  }
14897  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14898  if(!AtLocSuccessor(AVEntry2))
14899  {
14900  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' event (stationary train) is followed by event '" + AVEntry2.OneLineText
14901  + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14902  TrainDataVector.clear();
14903  Utilities->CallLogPop(2712);
14904  return(false);
14905  }
14906  }
14907  if(AVEntry.FormatType == TimeTimeLoc)
14908  {
14909  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14910  {
14911  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure can't be the last event for: " + TDEntry.HeadCode);
14912  TrainDataVector.clear();
14913  Utilities->CallLogPop(806);
14914  return(false);
14915  }
14916  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14917  if(!MovingSuccessor(AVEntry2))
14918  {
14919  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure event '" + AVEntry.OneLineText +
14920  "' (moving train) is followed by event '" + AVEntry2.OneLineText + "' (stationary train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
14921  TrainDataVector.clear();
14922  Utilities->CallLogPop(807);
14923  return(false);
14924  }
14925  if(!DwellTimeWarningGiven)
14926  {
14927  if(((double(AVEntry.DepartureTime - AVEntry.ArrivalTime)) > (0.1 / 86400)) && ((AVEntry.DepartureTime - AVEntry.ArrivalTime) < TDateTime((AVEntry.MinDwellTime - 0.05) / 86400)))
14928  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
14929  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
14930  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
14931  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
14932  DwellTimeWarningGiven = true;
14933  }
14934  if(((double(AVEntry.DepartureTime - AVEntry.ArrivalTime)) < (0.1 / 86400)) && (AVEntry.MinDwellTime > 30.05)) //i.e MDT of 30 ok for equal arr & dep times (30 is the default)
14935  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
14936  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
14937  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
14938  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
14939  DwellTimeWarningGiven = true;
14940  }
14941  }
14942  }
14943  if(AVEntry.FormatType == PassTime)
14944  {
14945  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14946  {
14947  SecondPassMessage(GiveMessages, "Error in timetable - a pass time can't be the last event for: " + TDEntry.HeadCode);
14948  TrainDataVector.clear();
14949  Utilities->CallLogPop(1530);
14950  return(false);
14951  }
14952  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14953  if(!MovingSuccessor(AVEntry2))
14954  {
14955  SecondPassMessage(GiveMessages, "Error in timetable - a pass time (moving train) is followed by event '" + AVEntry2.OneLineText + "' (stationary train) for: "
14956  + TDEntry.HeadCode + ". At least one is incorrect.");
14957  TrainDataVector.clear();
14958  Utilities->CallLogPop(1531);
14959  return(false);
14960  }
14961  }
14962  if(AVEntry.FormatType == Repeat)
14963  {
14964  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14965  {
14966  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is not the last item for: " + TDEntry.HeadCode);
14967  TrainDataVector.clear();
14968  Utilities->CallLogPop(808);
14969  return(false);
14970  }
14971  }
14972  }
14973  }
14974 
14975  // set arrival & departure times for TimeLocs & set their EventTimes to -1
14976  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14977  {
14978  bool DwellTimeWarningGiven = false;
14979  bool LastEntryIsAnArrival = false;
14980  LastArrivalTime = TDateTime(-1);
14981  DwellTimeWarningGiven = false;
14982  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14983  // first deal with unlocated Snt entries - so next entry (TimeLoc or TimeTimeLoc) is an arrival, all else stopped so the next TimeLoc is a departure
14984  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14985  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14986  // StartSpeed may or may not be 0, but train will move forwards (if capable of doing so), & next TimeLoc will be an arrival, whether or not after one or more TimeTimeLocs
14987  {
14988  LastEntryIsAnArrival = false;
14989  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14990  {
14991  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14992  if(AVEntry.FormatType == TimeLoc)
14993  {
14994  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14995  {
14996  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14997  }
14998  if(LastEntryIsAnArrival) //so this is a departure
14999  {
15000  AVEntry.DepartureTime = AVEntry.EventTime;
15001  AVEntry.EventTime = TDateTime(-1);
15002  LastEntryIsAnArrival = false;
15003  if((LastArrivalTime > TDateTime(0)) && !DwellTimeWarningGiven)
15004  {
15005  if(((double(AVEntry.DepartureTime - LastArrivalTime)) > (0.1 / 86400)) && ((AVEntry.DepartureTime - LastArrivalTime) < TDateTime((MinDwellTime - 0.05) / 86400)))
15006  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
15007  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
15008  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
15009  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
15010  DwellTimeWarningGiven = true;
15011  }
15012  if(((double(AVEntry.DepartureTime - LastArrivalTime)) < (0.1 / 86400)) && (MinDwellTime > 30.05)) //i.e MDT of 30 ok for equal arr & dep times (30 is the default)
15013  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
15014  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
15015  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
15016  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
15017  DwellTimeWarningGiven = true;
15018  }
15019  }
15020  if(AVEntry.MinDwellTime > 30.1)
15021  {
15022  SecondPassMessage(GiveMessages, "Error in timetable - a minimum dwell time is not permitted for a departure, "
15023  "add it to the corresponding arrival instead, see: " + TDEntry.HeadCode);
15024  TrainDataVector.clear();
15025  Utilities->CallLogPop(2742);
15026  return(false);
15027  }
15028  }
15029  else // last entry a departure so this is an arrival
15030  {
15031  AVEntry.ArrivalTime = AVEntry.EventTime;
15032  MinDwellTime = AVEntry.MinDwellTime;
15033  LastArrivalTime = AVEntry.ArrivalTime;
15034  AVEntry.EventTime = TDateTime(-1);
15035  LastEntryIsAnArrival = true;
15036  }
15037  }
15038  }
15039  }
15040  else // all others stopped at beginning
15041  {
15042  LastEntryIsAnArrival = true;
15043  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15044  {
15045  LastArrivalTime = TDateTime(-1);
15046  DwellTimeWarningGiven = false;
15047  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15048  if(AVEntry.FormatType == TimeLoc)
15049  {
15050  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
15051  {
15052  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
15053  }
15054  if(LastEntryIsAnArrival) //so this is a departure
15055  {
15056  AVEntry.DepartureTime = AVEntry.EventTime;
15057  AVEntry.EventTime = TDateTime(-1);
15058  LastEntryIsAnArrival = false;
15059  if((LastArrivalTime > TDateTime(0)) && !DwellTimeWarningGiven)
15060  {
15061  if(((double(AVEntry.DepartureTime - LastArrivalTime)) > (0.1 / 86400)) && ((AVEntry.DepartureTime - LastArrivalTime) < TDateTime((MinDwellTime - 0.05) / 86400)))
15062  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
15063  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
15064  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
15065  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
15066  DwellTimeWarningGiven = true;
15067  }
15068  if(((double(AVEntry.DepartureTime - LastArrivalTime)) < (0.1 / 86400)) && (MinDwellTime > 30.05)) //i.e MDT of 30 ok for equal arr & dep times (30 is the default)
15069  { //0.1 & 0.05 are to avoid rounding errors, if dep time - arr time negative the error will be caught elsewhere
15070  SecondPassMessage(GiveMessages, "Warning: Service " + TDEntry.HeadCode + " has a minimum dwell time that is greater than the timetabled stop duration for '" + AVEntry.OneLineText +
15071  "'. This is not an error but it will cause the train to run late and the 'Actions Due' panel to give inaccurate times.\n\n"
15072  "Note that this warning can't be given in every circumstance where a minimum dwell time is set too high so please try to avoid it.");
15073  DwellTimeWarningGiven = true;
15074  }
15075  }
15076  if(AVEntry.MinDwellTime > 30.1)
15077  {
15078  SecondPassMessage(GiveMessages, "Error in timetable - a minimum dwell time is not permitted for a departure, "
15079  "add it to the corresponding arrival instead, see: " + TDEntry.HeadCode);
15080  TrainDataVector.clear();
15081  Utilities->CallLogPop(2744);
15082  return(false);
15083  }
15084  }
15085  else // last entry a departure so this is an arrival
15086  {
15087  AVEntry.ArrivalTime = AVEntry.EventTime;
15088  MinDwellTime = AVEntry.MinDwellTime;
15089  LastArrivalTime = AVEntry.ArrivalTime;
15090  AVEntry.EventTime = TDateTime(-1);
15091  LastEntryIsAnArrival = true;
15092  }
15093  }
15094  }
15095  }
15096  }
15097  // perform remaining successor checks for TimeLocs
15098  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15099  {
15100  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15101  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15102  {
15103  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15104  if((AVEntry.FormatType == TimeLoc) && (AVEntry.ArrivalTime >= TDateTime(0))) // arrival
15105  // TimeLoc (arr) -> No starts, repeats, Fer or TimeTimeLoc; TimeLoc (dep) or any other OK
15106  {
15107  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
15108  {
15109  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival can't be the last event for: " + TDEntry.HeadCode);
15110  TrainDataVector.clear();
15111  Utilities->CallLogPop(809);
15112  return(false);
15113  }
15114  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
15115  if(!AtLocSuccessor(AVEntry2))
15116  {
15117  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival (stationary train) is followed by event '"
15118  + AVEntry2.OneLineText + "' (moving train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
15119  TrainDataVector.clear();
15120  Utilities->CallLogPop(810);
15121  return(false);
15122  }
15123  }
15124  if((AVEntry.FormatType == TimeLoc) && (AVEntry.DepartureTime >= TDateTime(0))) // departure
15125  // TimeLoc (dep) -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas OK, no others
15126  {
15127  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
15128  {
15129  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure can't be the last event for: " + TDEntry.HeadCode);
15130  TrainDataVector.clear();
15131  Utilities->CallLogPop(811);
15132  return(false);
15133  }
15134  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
15135  if(!MovingSuccessor(AVEntry2))
15136  {
15137  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure (moving train) is followed by event '" + AVEntry2.OneLineText
15138  + "' (stationary train) for: " + TDEntry.HeadCode + ". At least one is incorrect.");
15139  TrainDataVector.clear();
15140  Utilities->CallLogPop(812);
15141  return(false);
15142  }
15143  }
15144  }
15145  }
15146 
15147  // check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1,
15148  // & repeats have no times set
15149  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15150  {
15151  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15152  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15153  {
15154  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15155  if(AVEntry.FormatType == TimeLoc)
15156  {
15157  if(AVEntry.EventTime != TDateTime(-1))
15158  {
15159  throw Exception("Timetable error, TimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
15160  }
15161  if((AVEntry.ArrivalTime == TDateTime(-1)) && (AVEntry.DepartureTime == TDateTime(-1)))
15162  {
15163  throw Exception("Timetable error, TimeLoc event has neither arrival nor departure time set for " + TDEntry.HeadCode);
15164  }
15165  }
15166  if(AVEntry.FormatType == TimeTimeLoc)
15167  {
15168  if(AVEntry.EventTime != TDateTime(-1))
15169  {
15170  throw Exception("Timetable error, TimeTimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
15171  }
15172  if((AVEntry.ArrivalTime == TDateTime(-1)) || (AVEntry.DepartureTime == TDateTime(-1)))
15173  {
15174  throw Exception("Timetable error, TimeTimeLoc event has either arrival or departure time not set for " + TDEntry.HeadCode);
15175  }
15176  }
15177  if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == StartNew) ||
15178  (AVEntry.FormatType == SNTShuttle) || (AVEntry.FormatType == SNSShuttle) || (AVEntry.FormatType == FNSNonRepeatToShuttle) ||
15179  (AVEntry.FormatType == FSHNewService) || (AVEntry.FormatType == PassTime))
15180  {
15181  if(AVEntry.EventTime == TDateTime(-1))
15182  {
15183  throw Exception("Timetable error, Cmd or PassTime event has EventTime not set for " + TDEntry.HeadCode);
15184  }
15185  if((AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
15186  {
15187  throw Exception("Timetable error, Cmd or PassTime event has either arrival or departure time set for " + TDEntry.HeadCode);
15188  }
15189  }
15190  if(AVEntry.FormatType == Repeat)
15191  {
15192  if((AVEntry.EventTime != TDateTime(-1)) || (AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
15193  {
15194  throw Exception("Timetable error, Repeat event has a time set for " + TDEntry.HeadCode);
15195  }
15196  }
15197  }
15198  }
15199 
15200  // check times stay same or increase, note that can have time of 0 if include midnight
15201  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15202  {
15203  TDateTime CurrentTime = TTClockTime; // the timetable start time
15204  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15205  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15206  {
15207  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15208  if(AVEntry.FormatType == Repeat)
15209  {
15210  break;
15211  }
15212  if(AVEntry.FormatType == FinRemHere)
15213  {
15214  break;
15215  }
15216  if(AVEntry.FormatType == TimeTimeLoc)
15217  {
15218  if(AVEntry.DepartureTime < AVEntry.ArrivalTime)
15219  {
15220  SecondPassMessage(GiveMessages, "Error in timetable - timed arrival and departure '" + AVEntry.OneLineText
15221  + "' has a later arrival than departure time for: " + TDEntry.HeadCode);
15222  TrainDataVector.clear();
15223  Utilities->CallLogPop(813);
15224  return(false);
15225  }
15226  if(AVEntry.ArrivalTime < CurrentTime)
15227  {
15228  SecondPassMessage(GiveMessages, "Error in timetable - timed arrival and departure '" + AVEntry.OneLineText
15229  + "' has too early an arrival time for: " + TDEntry.HeadCode);
15230  TrainDataVector.clear();
15231  Utilities->CallLogPop(814);
15232  return(false);
15233  }
15234  CurrentTime = AVEntry.DepartureTime;
15235  continue;
15236  }
15237  if(AVEntry.FormatType == TimeLoc)
15238  {
15239  if(AVEntry.ArrivalTime >= TDateTime(0))
15240  {
15241  if(AVEntry.ArrivalTime < CurrentTime)
15242  {
15243  SecondPassMessage(GiveMessages, "Error in timetable - timed arrival event '" + AVEntry.OneLineText
15244  + "' has a time that is too early for: " + TDEntry.HeadCode);
15245  TrainDataVector.clear();
15246  Utilities->CallLogPop(815);
15247  return(false);
15248  }
15249  CurrentTime = AVEntry.ArrivalTime;
15250  }
15251  else
15252  {
15253  if(AVEntry.DepartureTime < CurrentTime)
15254  // both may be 0 legitimately so must allow for this
15255  {
15256  SecondPassMessage(GiveMessages, "Error in timetable - timed departure event '" + AVEntry.OneLineText
15257  + "' has a time that is too early for: " + TDEntry.HeadCode);
15258  TrainDataVector.clear();
15259  Utilities->CallLogPop(816);
15260  return(false);
15261  }
15262  CurrentTime = AVEntry.DepartureTime;
15263  }
15264  continue;
15265  }
15266  if(AVEntry.EventTime < CurrentTime)
15267  // all others have EventTime set
15268  {
15269  SecondPassMessage(GiveMessages, "Error in timetable - event '" + AVEntry.OneLineText
15270  + "' has a time that is set too early for: " + TDEntry.HeadCode + ".");
15271  TrainDataVector.clear();
15272  Utilities->CallLogPop(835);
15273  return(false);
15274  }
15275  CurrentTime = AVEntry.EventTime;
15276  continue;
15277  }
15278  }
15279 
15280  // check locations consistent
15281  AnsiString LastLocationName = "";
15282 
15283  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15284  {
15285  bool LastEntryIsAnArrival = false;
15286  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15287  // first deal with moving Snt entries (all else stopped)
15288  if((TrainDataVector.at(x).ActionVector.at(0).Command == "Snt") && (TrainDataVector.at(x).ActionVector.at(0).LocationType == EnRoute))
15289  {
15290  LastEntryIsAnArrival = false;
15291  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName; // should be ""
15292  if(LastLocationName != "")
15293  {
15294  throw Exception("Timetable error, moving Snt event has LocationName set for " + TDEntry.HeadCode);
15295  }
15296  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size();
15297  y++) // note that immediate successor to a moving Snt can only be a Moving type
15298  {
15299  // if it's a SignallerControl entry then the condition isn't met
15300  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15301  if(AVEntry.FormatType == Repeat)
15302  {
15303  break; // repeat = reached end (+allows repeat after signaller controlled entry)
15304  }
15305  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
15306  {
15307  if(AVEntry.LocationName != LastLocationName)
15308  {
15309  SecondPassMessage(GiveMessages, "Error in timetable - event '" + AVEntry.OneLineText
15310  + "' is at a location that differs from the earlier event for: " + TDEntry.HeadCode);
15311  TrainDataVector.clear();
15312  Utilities->CallLogPop(823);
15313  return(false);
15314  }
15315  }
15316  else if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdDescription) || (AVEntry.FormatType == TimeCmdMaxSpeed))
15317  // cdt is the only TimeCmd, dsc is the only TimeCmdDescription & cms is the only TimeCmdMaxSpeed
15318  {
15319  if(AVEntry.LocationName != LastLocationName)
15320  {
15321  SecondPassMessage(GiveMessages, "Error in timetable - event '" + AVEntry.OneLineText
15322  + "' is at a location that differs from the earlier event for: " + TDEntry.HeadCode);
15323  TrainDataVector.clear();
15324  Utilities->CallLogPop(824);
15325  return(false);
15326  }
15327  }
15328  else if(AVEntry.FormatType == TimeTimeLoc)
15329  {
15330  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15331  // last entry must be a departure or would have failed earlier
15332  {
15333  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15334  TwoLocationFlag = true;
15335 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15336 // TwoOrMoreLocationsWarningGiven = true;
15337  }
15338  LastLocationName = AVEntry.LocationName;
15339  LastEntryIsAnArrival = false;
15340  }
15341  else if(AVEntry.FormatType == TimeLoc)
15342  {
15343  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
15344  {
15345  SecondPassMessage(GiveMessages, "Error in timetable - departure event '" + AVEntry.OneLineText
15346  + "' is at a location that differs from the arrival location for: " + TDEntry.HeadCode);
15347  TrainDataVector.clear();
15348  Utilities->CallLogPop(826);
15349  return(false);
15350  }
15351  else if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName))
15352  {
15353  SecondPassMessage(GiveMessages, "Error in timetable - arrival event '" + AVEntry.OneLineText
15354  + "' is at the same location as the earlier departure event for: " + TDEntry.HeadCode);
15355  TrainDataVector.clear();
15356  Utilities->CallLogPop(827);
15357  return(false);
15358  }
15359  LastLocationName = AVEntry.LocationName;
15360  LastEntryIsAnArrival = !LastEntryIsAnArrival;
15361  }
15362  }
15363  }
15364  else // all stationary starting entries
15365  {
15366  LastEntryIsAnArrival = true;
15367  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName;
15368  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15369  {
15370  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15371  if(AVEntry.FormatType == Repeat)
15372  {
15373  break;
15374  }
15375  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
15376  // no need to add anything for shuttle starts since they are at loc (0) anyway
15377  {
15378  if(AVEntry.LocationName != LastLocationName)
15379  {
15380  SecondPassMessage(GiveMessages, "Error in timetable - event '" + AVEntry.OneLineText
15381  + "' is at a location that differs from the earlier event for: " + TDEntry.HeadCode);
15382  TrainDataVector.clear();
15383  Utilities->CallLogPop(828);
15384  return(false);
15385  }
15386  }
15387  else if(AVEntry.FormatType == TimeCmd)
15388  // cdt is the only TimeCmd
15389  {
15390  if(AVEntry.LocationName != LastLocationName)
15391  {
15392  SecondPassMessage(GiveMessages, "Error in timetable - event '" + AVEntry.OneLineText
15393  + "' is at a location that differs from the earlier event for: " + TDEntry.HeadCode);
15394  TrainDataVector.clear();
15395  Utilities->CallLogPop(829);
15396  return(false);
15397  }
15398  }
15399  else if(AVEntry.FormatType == TimeTimeLoc)
15400  {
15401  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15402  // last entry must be a departure or would have failed earlier
15403  {
15404  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15405  TwoLocationFlag = true;
15406 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15407 // TwoOrMoreLocationsWarningGiven = true;
15408  }
15409  LastLocationName = AVEntry.LocationName;
15410  LastEntryIsAnArrival = false;
15411  }
15412  else if(AVEntry.FormatType == TimeLoc)
15413  {
15414  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
15415  {
15416  SecondPassMessage(GiveMessages, "Error in timetable - departure event '" + AVEntry.OneLineText
15417  + "' is at a location that differs from the earlier arrival event for: " + TDEntry.HeadCode);
15418  TrainDataVector.clear();
15419  Utilities->CallLogPop(831);
15420  return(false);
15421  }
15422  if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName) && !TwoOrMoreLocationsWarningGiven)
15423  {
15424  SecondPassMessage(GiveMessages, "Warning - arrival event '" + AVEntry.OneLineText
15425  + "' is at the same location as the earlier departure event for: " + TDEntry.HeadCode + ". This may be an error.");
15427 // TrainDataVector.clear();
15428 // Utilities->CallLogPop(832);
15429 // return false;
15430  }
15431  LastLocationName = AVEntry.LocationName;
15432  LastEntryIsAnArrival = !LastEntryIsAnArrival;
15433  }
15434  }
15435  }
15436  }
15437 
15438  // Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0)
15439  // i.e. same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0
15440  AnsiString LocationNameToBeChecked = "";
15441 
15442  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15443  {
15444  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15445  unsigned int y = 0;
15446  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
15447  // first discard unlocated Snt entries as they don't have location name set
15448  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
15449  {
15450  y = 1;
15451  }
15452  while(y < TDEntry.ActionVector.size())
15453  // need to check each location name separately in turn, skipped for SignallerControl entries
15454  {
15455  if((TDEntry.ActionVector.at(y).Command == "Fer") || (TDEntry.ActionVector.at(y).FormatType == Repeat))
15456  {
15457  break; // out of the 'while' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15458  }
15459  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName;
15460  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
15461  {
15462  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
15463  if((AVEntry.Command == "Fer") || (AVEntry.FormatType == Repeat))
15464  {
15465  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15466  }
15467  if(AVEntry.Command == "cdt")
15468  {
15469  break; // out of the 'z' loop since the check is only valid up to a change of direction
15470  }
15471  if(AVEntry.LocationName == LocationNameToBeChecked)
15472  {
15473  continue; // keep going while name same
15474  }
15475  if(AVEntry.LocationName != LocationNameToBeChecked)
15476  // if name different check forwards to see if repeats
15477  {
15478  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
15479  {
15480  if(TDEntry.ActionVector.at(a).Command == "cdt")
15481  {
15482  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
15483  }
15484  if((TDEntry.ActionVector.at(a).LocationName == LocationNameToBeChecked) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15485  {
15486  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15487  TwoLocationFlag = true;
15488 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15489 // TwoOrMoreLocationsWarningGiven = true;
15490  }
15491  }
15492  break; // out of the 'z' loop since have checked 'a' as far as need to
15493  }
15494  }
15495  y++;
15496  }
15497  }
15498  if(TwoLocationFlag) //messages for this moved to InterfaceUnit - separate box listing potential errors and asking user to check
15499  {
15500  TwoLocationList.sort(); //need to sort first in alphabetical order to ensure all duplictes removed
15501  TwoLocationList.unique(); //remove duplicates
15502  }
15503 
15504  // check all locations except unlocated 'Snt' & 'Fer' have LocationName set
15505  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15506  {
15507  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15508  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15509  {
15510  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15511  if((AVEntry.LocationName == "") && (AVEntry.Command != "Snt") && (AVEntry.Command != "Fer") && (AVEntry.FormatType != Repeat))
15512  {
15513  throw Exception("Error, non- 'Snt', 'Fer' or Repeat event doesn't have a location name set for " + TDEntry.HeadCode);
15514  }
15515  AnsiString LocName = "";
15516  // dummy, only used so can call IsSNTEntryLocated
15517  if((AVEntry.Command == "Snt") && (IsSNTEntryLocated(1, TrainDataVector.at(x), LocName)))
15518  {
15519  if(AVEntry.LocationName == "")
15520  {
15521  throw Exception("Error, 'Snt' event at a stop location doesn't have a location name set for " + TDEntry.HeadCode);
15522  }
15523  }
15524  if((AVEntry.Command == "Snt") && !(IsSNTEntryLocated(2, TrainDataVector.at(x), LocName)))
15525  {
15526  if(AVEntry.LocationName != "")
15527  {
15528  throw Exception("Error, 'Snt' unlocated event has a location name set for " + TDEntry.HeadCode);
15529  }
15530  }
15531  }
15532  }
15533 
15534 /* Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
15535  Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
15536  Check each change of headcode not repeated anywhere else (anywhere, not just for one train, since headcodes can be duplicated)
15537 
15538  i.e. check everywhere where there is an 'OtherHeadCode' that it matches once only with its reference (both ways) + set
15539  the OtherHeadCodeStartingEntryPtr pointers where appropriate + train information for splits & new services
15540 
15541  BUT need to separate the shuttles from non-shuttles, because can have two trains reference each other in both forms,
15542  eg 2F44 Sns-sh ends in Fns to 2F45, & Sns 2F45 ends in Fns-sh to 2F44. Here 2F45 is the 'OtherHeadCode' for both
15543  Sns-sh & Fns in train 2F44, & 2F44 is the 'OtherHeadCode' for both Sns & Fns-sh in train 2F45.
15544 */
15545  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // new test to ensure no duplicate links at all, other checks ensure none for shuttles,
15546  {
15547  // non-shuttles & non-repeating links separately, but don't check that there isn't a
15548  // duplicate between a non-repeating shuttle and another - leave original tests in as
15549  // these also set the pointers
15550  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15551  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15552  {
15553  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15554  if(AVEntry.OtherHeadCode != "")
15555  {
15556  if(!CheckForDuplicateCrossReferences(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
15557  {
15558  Utilities->CallLogPop(1584);
15559  return(false); // error message given in called function
15560  }
15561  }
15562  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15563  {
15564  if(!CheckForDuplicateCrossReferences(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
15565  {
15566  Utilities->CallLogPop(1585);
15567  return(false); // error message given in called function
15568  }
15569  }
15570  }
15571  }
15572 
15573  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15574  {
15575  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15576  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15577  {
15578  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15579  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15580  {
15581  if(AVEntry.OtherHeadCode != "")
15582  {
15583  if(!CheckCrossReferencesAndSetData(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, true, GiveMessages))
15584  // false = non-shuttle
15585  {
15586  Utilities->CallLogPop(864);
15587  return(false); // error message given in called function
15588  }
15589  }
15590  }
15591  }
15592  }
15593 
15594  // now repeat the check just for the shuttles
15595  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15596  {
15597  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15598  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15599  {
15600  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15601  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
15602  {
15603  if(AVEntry.OtherHeadCode != "")
15604  {
15605  if(!CheckCrossReferencesAndSetData(1, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, true, GiveMessages))
15606  // true = shuttle
15607  {
15608  Utilities->CallLogPop(1100);
15609  return(false); // error message given in called function
15610  }
15611  }
15612  }
15613  }
15614  }
15615 
15616  // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
15617  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15618  {
15619  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15620  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15621  {
15622  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15623  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15624  {
15625  if(!CheckNonRepeatingShuttleLinksAndSetData(0, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, true, GiveMessages))
15626  {
15627  Utilities->CallLogPop(1060);
15628  return(false); // error message given in called function
15629  }
15630  }
15631  }
15632  }
15633 
15634  // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
15635  // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
15636  // don't ever need to and as designed would skip repeats
15637  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15638  {
15639  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15640  {
15641  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15642  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh"))
15643  {
15644  if(!CheckShuttleServiceIntegrity(0, &(TrainDataVector.at(x)), GiveMessages))
15645  {
15646  Utilities->CallLogPop(1090);
15647  return(false); // error message given in called function
15648  }
15649  }
15650  }
15651  }
15652 
15653  // check all entries have all types set to something
15654  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15655  {
15656  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15657  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15658  {
15659  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15660  if(AVEntry.FormatType == NoFormat)
15661  {
15662  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has FormatType unset for: " + TDEntry.HeadCode);
15663  }
15664  else if(AVEntry.SequenceType == NoSequence)
15665  {
15666  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has SequenceType unset for: " + TDEntry.HeadCode);
15667  }
15668  else if(AVEntry.LocationType == NoLocation)
15669  {
15670  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has LocationType unset for: " + TDEntry.HeadCode);
15671  }
15672  else if(AVEntry.ShuttleLinkType == NoShuttleLink)
15673  {
15674  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has ShuttleLinkType unset for: " + TDEntry.HeadCode);
15675  }
15676  }
15677  }
15678 
15679  // all OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
15680  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15681  {
15682  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15683  // non-const reference so can alter content
15684  TTrainOperatingData TData;
15685  const TActionVectorEntry &LastAVEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
15686  if(LastAVEntry.FormatType == Repeat) // check if a repeat
15687  {
15688 /*
15689  class TTrainOperatingData
15690  {
15691  public:
15692  int TrainID; - default, set at construction
15693  TActionEventType EventReported; used during operation
15694  TRunningEntry RunningEntry; - default, set at construction
15695  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;} //constructor, values set to defaults
15696  };
15697 */
15698  TDEntry.NumberOfTrains = LastAVEntry.NumberOfRepeats + 1;
15699  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
15700  {
15701  TDEntry.TrainOperatingDataVector.push_back(TData);
15702  }
15703  }
15704  else
15705  {
15706  TDEntry.NumberOfTrains = 1;
15707  }
15708  }
15709 
15710  // check that don't include any Continuation names
15711  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15712  {
15713  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15714  {
15715  AnsiString LocName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
15716  AnsiString HC = TrainDataVector.at(x).HeadCode;
15717  if(LocName != "")
15718  {
15719  if(Track->ContinuationNameMap.find(LocName) != Track->ContinuationNameMap.end())
15720  {
15721  SecondPassMessage(GiveMessages, "Error in timetable - a continuation name (" + LocName + ") must not be included in the timetable, see service " + HC);
15722  TrainDataVector.clear();
15723  Utilities->CallLogPop(1578);
15724  return(false);
15725  }
15726  }
15727  }
15728  }
15729 
15730  // check that all repeat times below 96h
15731  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15732  {
15733  int NumRepeats = (TrainDataVector.at(x).NumberOfTrains) - 1;
15734  int IncMinutes = 0;
15735  if(TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).FormatType == Repeat)
15736  {
15737  IncMinutes = TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).RearStartOrRepeatMins;
15738  }
15739  else
15740  {
15741  continue; // basic times already checked in CheckTimeValidity
15742  }
15743  AnsiString HC = TrainDataVector.at(x).HeadCode;
15744  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15745  {
15746  if((double)TrainDataVector.at(x).ActionVector.at(y).EventTime > -1)
15747  {
15748  if(((double)GetRepeatTime(32, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15749  {
15750  SecondPassMessage(GiveMessages, "Error in timetable - a repeat time exceeds 95h 59m, see service " + HC); // 3d 23h 59m = 3.9993055556
15751  TrainDataVector.clear();
15752  Utilities->CallLogPop(1818);
15753  return(false);
15754  }
15755  }
15756  if((double)TrainDataVector.at(x).ActionVector.at(y).ArrivalTime > -1)
15757  {
15758  if(((double)GetRepeatTime(33, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15759  // 3d 23h 59m = 3.9993055556
15760  {
15761  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15762  TrainDataVector.clear();
15763  Utilities->CallLogPop(1819);
15764  return(false);
15765  }
15766  }
15767  if((double)TrainDataVector.at(x).ActionVector.at(y).DepartureTime > -1)
15768  {
15769  if(((double)GetRepeatTime(34, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15770  // 3d 23h 59m = 3.9993055556
15771  {
15772  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15773  TrainDataVector.clear();
15774  Utilities->CallLogPop(1820);
15775  return(false);
15776  }
15777  }
15778  }
15779  }
15780 
15781  // Now that all set up change any extended headcodes back to ordinary headcodes
15782  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15783  {
15784  StripExcessFromHeadCode(0, TrainDataVector.at(x).HeadCode);
15785  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15786  {
15787  StripExcessFromHeadCode(1, TrainDataVector.at(x).ActionVector.at(y).OtherHeadCode);
15788  StripExcessFromHeadCode(2, TrainDataVector.at(x).ActionVector.at(y).NonRepeatingShuttleLinkHeadCode);
15789  }
15790  }
15791 
15792  // SaveTrainDataVectorToFile(0);//for testing purposes
15794  Utilities->CallLogPop(782);
15795  return(true);
15796 }
15797 
15798 // ---------------------------------------------------------------------------
15799 // Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer; Only call this when moving so if TimeLoc found it must be a departure
15801 {
15802  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.FormatType == TimeTimeLoc) || (AVEntry.Command == "pas") || (AVEntry.Command == "Fer"));
15803 }
15804 
15805 // ---------------------------------------------------------------------------
15806 // AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/cms/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs; Only call this when stationary so if TimeLoc found it must be an arrival
15808 {
15809  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") ||
15810  (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") ||
15811  (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "cms"));
15812 }
15813 
15814 // ---------------------------------------------------------------------------
15815 
15816 void TTrainController::StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
15817 {
15818  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripExcessFromHeadCode," + HeadCode);
15819  if(HeadCode.Length() > 4) // ignore otherwise
15820  {
15821  HeadCode = HeadCode.SubString(HeadCode.Length() - 3, 4);
15822  }
15823  Utilities->CallLogPop(1593);
15824 }
15825 
15826 // ---------------------------------------------------------------------------
15827 
15828 bool TTrainController::CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
15829 {
15830  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckForDuplicateCrossReferences," + MainHeadCode + "," +
15831  SecondHeadCode);
15832  int ForwardCount = 0;
15833  int ReverseCount = 0;
15834 
15835  if(MainHeadCode == SecondHeadCode)
15836  {
15837  SecondPassMessage(GiveMessages, "Error in timetable - Service " + MainHeadCode + " has an event that references itself");
15838  TrainDataVector.clear();
15839  Utilities->CallLogPop(1594);
15840  return(false);
15841  }
15842  // forward check
15843  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15844  {
15845  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15846  if(TDEntry.HeadCode == MainHeadCode)
15847  {
15848  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15849  {
15850  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15851  if(AVEntry.OtherHeadCode == SecondHeadCode)
15852  {
15853  ForwardCount++;
15854  }
15855  if(AVEntry.NonRepeatingShuttleLinkHeadCode == SecondHeadCode)
15856  // need own check in case both 'Other' & 'NonRepeating' have same headcode
15857  {
15858  ForwardCount++;
15859  }
15860  }
15861  }
15862  }
15863  if(ForwardCount == 0)
15864  // this is an exception because the headcodes are selected in the same order as the forward check
15865  {
15866  throw Exception("Error, ForwardCount == 0 in CheckForDuplicateCrossReferences after called with found values");
15867  }
15868  if(ForwardCount > 2)
15869  // can have 2 if one is Sns-sh linking from another leg of the shuttle, and Fns links out to that same leg
15870  {
15871  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + SecondHeadCode + " from a train whose headcode is " +
15872  MainHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15873  TrainDataVector.clear();
15874  Utilities->CallLogPop(1587);
15875  return(false);
15876  }
15877  // reverse check
15878  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15879  {
15880  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15881  if(TDEntry.HeadCode == SecondHeadCode)
15882  {
15883  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15884  {
15885  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15886  if(AVEntry.OtherHeadCode == MainHeadCode)
15887  {
15888  ReverseCount++;
15889  }
15890  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
15891  {
15892  ReverseCount++;
15893  }
15894  }
15895  }
15896  }
15897 
15898  if(ReverseCount == 0)
15899  {
15900  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + SecondHeadCode);
15901  TrainDataVector.clear();
15902  Utilities->CallLogPop(1588);
15903  return(false);
15904  }
15905  if(ReverseCount > 2)
15906  // can have 2 if one is a second shuttle leg with a link in from Fns, and it links out to the same service with Fxx-sh
15907  {
15908  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + MainHeadCode + " from a train whose headcode is " +
15909  SecondHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15910  TrainDataVector.clear();
15911  Utilities->CallLogPop(1589);
15912  return(false);
15913  }
15914  if(ForwardCount != ReverseCount)
15915  {
15916  SecondPassMessage(GiveMessages, "Error in timetable - " + MainHeadCode + " has a different number of references to " + SecondHeadCode +
15917  " than the other way round");
15918  TrainDataVector.clear();
15919  Utilities->CallLogPop(1610);
15920  return(false);
15921  }
15922  Utilities->CallLogPop(1590);
15923  return(true);
15924 }
15925 
15926 // ---------------------------------------------------------------------------
15927 
15928 bool TTrainController::CheckCrossReferencesAndSetData(int Caller, AnsiString MainHeadCode, AnsiString OtherHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
15929 /* Return false for no find or more than one find, check correct types of link
15930  First run through all trains whose headcode is the MainHeadCode (may be > 1) & for each entry whose
15931  'other' is OtherHeadCode increment a forward counter. Keep a pointer to the 'OtherHeadCode' entry for use later
15932  Must be exactly 1 forward count. NB Forward relates to MainHeadCode
15933  Then do the same in reverse.
15934  Using the pointers check the event times, then check that the locations & commands match - if main is a split then other must be Sfs;
15935  if main is Fns other must be Sns; if main is jbo other must be Fjo.
15936  Also check platform lengths OK for a split location (call to Track function for this - at least one platform at location has to be long
15937  enough). If all succeeds so far set the relevant OtherHeadCodeStartingEntryPtr to the new service starting point + train information
15938  for Sfs & Sns services. Finally check the repeat entries if present are consistent
15939 
15940  Check all except the NonRepeatingShuttleLinkHeadCodes, which only occur from F-nshs to Sns-sh, and from Fns-sh to
15941  Sns-fsh. All others should check out OK, but check shuttles & non-shuttles separately.
15942 
15943  /NB prohibit main & other headcodes being same, causes probs in failing to recognise locations
15944 */
15945 
15946 {
15947  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckCrossReferencesAndSetData," + MainHeadCode + "," + OtherHeadCode);
15948  int ForwardCount = 0;
15949  int ReverseCount = 0;
15950  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
15951  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
15952  TTrainDataEntry *MainTrainDataPtr = 0;
15953  TTrainDataEntry *OtherTrainDataPtr = 0;
15954 
15955  // forward check
15956  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15957  {
15958  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15959  if(TDEntry.HeadCode == MainHeadCode)
15960  {
15961  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15962  {
15963  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15964  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15965  {
15966  if(AVEntry.OtherHeadCode == OtherHeadCode)
15967  {
15968  MainTrainDataPtr = &TrainDataVector.at(x);
15969  ForwardEntryPtr = &AVEntry;
15970  ForwardCount++;
15971  ForwardTDVectorNumber = x;
15972  }
15973  }
15974  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") ||
15975  (AVEntry.Command == "Frh-sh")))
15976  {
15977  if(AVEntry.OtherHeadCode == OtherHeadCode)
15978  {
15979  MainTrainDataPtr = &TrainDataVector.at(x);
15980  ForwardEntryPtr = &AVEntry;
15981  ForwardCount++;
15982  ForwardTDVectorNumber = x;
15983  }
15984  }
15985  }
15986  }
15987  }
15988  if(ForwardCount == 0)
15989  // this is an exception because the headcodes are selected in the same order as the forward check
15990  {
15991  throw Exception("Error, ForwardCount == 0 in CheckCrossReferencesAndSetData after called with found values");
15992  }
15993  if(ForwardCount > 1)
15994  {
15995  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + OtherHeadCode + " from a train whose headcode is " +
15996  MainHeadCode);
15997  TrainDataVector.clear();
15998  Utilities->CallLogPop(836);
15999  return(false);
16000  }
16001  // reverse check
16002  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16003  {
16004  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16005  if(TDEntry.HeadCode == OtherHeadCode)
16006  {
16007  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16008  {
16009  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16010  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
16011  {
16012  if(AVEntry.OtherHeadCode == MainHeadCode)
16013  {
16014  OtherTrainDataPtr = &TrainDataVector.at(x);
16015  ReverseCount++;
16016  ReverseEntryPtr = &AVEntry;
16017  ReverseTDVectorNumber = x;
16018  }
16019  }
16020  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh")))
16021  {
16022  if(AVEntry.OtherHeadCode == MainHeadCode)
16023  {
16024  OtherTrainDataPtr = &TrainDataVector.at(x);
16025  ReverseCount++;
16026  ReverseEntryPtr = &AVEntry;
16027  ReverseTDVectorNumber = x;
16028  }
16029  }
16030  }
16031  }
16032  }
16033 
16034  if(ReverseCount == 0)
16035  {
16036  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + OtherHeadCode);
16037  TrainDataVector.clear();
16038  Utilities->CallLogPop(837);
16039  return(false);
16040  }
16041  if(ReverseCount > 1)
16042  {
16043  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
16044  OtherHeadCode);
16045  TrainDataVector.clear();
16046  Utilities->CallLogPop(838);
16047  return(false);
16048  }
16049  // these will all be false for !Shuttle
16050  bool ForwardShuttleStart = ((ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"));
16051  bool ForwardShuttleFinish = ((ForwardEntryPtr->Command == "Fns-sh") || (ForwardEntryPtr->Command == "Frh-sh"));
16052  bool ReverseShuttleStart = ((ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"));
16053  bool ReverseShuttleFinish = ((ReverseEntryPtr->Command == "Fns-sh") || (ReverseEntryPtr->Command == "Frh-sh"));
16054 
16055  if(Shuttle && MainTrainDataPtr->ActionVector.back().FormatType != Repeat)
16056  {
16057  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat");
16058  TrainDataVector.clear();
16059  Utilities->CallLogPop(1058);
16060  return(false);
16061  }
16062  if(Shuttle && OtherTrainDataPtr->ActionVector.back().FormatType != Repeat)
16063  {
16064  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + OtherHeadCode + " does not have a repeat");
16065  TrainDataVector.clear();
16066  Utilities->CallLogPop(1059);
16067  return(false);
16068  }
16069  if(SetDataAndCheckLocations)
16070  {
16071  if(ForwardEntryPtr->LocationName == "")
16072  {
16073  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
16074  ". One or other service does not have a location set");
16075  TrainDataVector.clear();
16076  Utilities->CallLogPop(526);
16077  return(false);
16078  }
16079  if(ReverseEntryPtr->LocationName == "")
16080  {
16081  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
16082  ". One or other service does not have a location set");
16083  TrainDataVector.clear();
16084  Utilities->CallLogPop(527);
16085  return(false);
16086  }
16087  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
16088  {
16089  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
16090  " is at a different location to the referencing train " + MainHeadCode);
16091  TrainDataVector.clear();
16092  Utilities->CallLogPop(842);
16093  return(false);
16094  }
16095  }
16096  // ignore shuttle repeat links for first time check
16097  if(!Shuttle)
16098  {
16099  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
16100  {
16101  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
16102  " has a different event time to the referencing train " + MainHeadCode);
16103  TrainDataVector.clear();
16104  Utilities->CallLogPop(525);
16105  return(false);
16106  }
16107  }
16108  // need to allow for repeat times multiplying up by repeating time for shuttle repeat links
16109  // no need to check from reverse to forward as already checked links consistent, and if include will send message twice
16110  if(ForwardShuttleStart && ReverseShuttleFinish)
16111  // Shuttle must be true if these are true
16112  {
16113  if(!CheckShuttleRepeatTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime, OtherTrainDataPtr->ActionVector.back().RearStartOrRepeatMins))
16114  {
16115  SecondPassMessage(GiveMessages, "Error in timetable - shuttle service " + MainHeadCode +
16116  " first repeat restart time not consistent with finish service " + OtherHeadCode);
16117  TrainDataVector.clear();
16118  Utilities->CallLogPop(1055);
16119  return(false);
16120  }
16121  }
16122  if((ReverseEntryPtr->Command == "Sfs") || (ReverseEntryPtr->Command == "Sns"))
16123  // doesn't matter about ForwardEntryPtr being Sfs/Sns as called for every occurrence of an 'OtherHeadCode' so won't escape
16124  {
16125  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16126  {
16127  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' or 'Sns' event (" + OtherHeadCode +
16128  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16129  TrainDataVector.clear();
16130  Utilities->CallLogPop(528);
16131  return(false);
16132  }
16133  }
16134  if(ReverseEntryPtr->Command == "Fjo")
16135  // doesn't matter about ForwardEntryPtr being Fjo as called for every occurrence of an 'OtherHeadCode' so won't escape
16136  {
16137  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16138  {
16139  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fjo' event (" + OtherHeadCode +
16140  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16141  TrainDataVector.clear();
16142  Utilities->CallLogPop(862);
16143  return(false);
16144  }
16145  }
16146  if(ReverseEntryPtr->Command == "Fns")
16147  // doesn't matter about ForwardEntryPtr being Fns as called for every occurrence of an 'OtherHeadCode' so won't escape
16148  {
16149  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16150  {
16151  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fns' event (" + OtherHeadCode +
16152  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16153  TrainDataVector.clear();
16154  Utilities->CallLogPop(529);
16155  return(false);
16156  }
16157  }
16158  if(ForwardEntryPtr->Command == "Sfs")
16159  {
16160  if((ReverseEntryPtr->Command != "fsp") && (ReverseEntryPtr->Command != "rsp"))
16161  {
16162  SecondPassMessage(GiveMessages,
16163  "Error in timetable - unable to find a corresponding split train event for the train that starts from a split whose headcode is " +
16164  MainHeadCode);
16165  TrainDataVector.clear();
16166  Utilities->CallLogPop(530);
16167  return(false);
16168  }
16169  }
16170  if((ForwardEntryPtr->Command == "fsp") || (ForwardEntryPtr->Command == "rsp"))
16171  {
16172  if(ReverseEntryPtr->Command != "Sfs")
16173  {
16174  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sfs' event for the train split whose headcode is " +
16175  MainHeadCode);
16176  TrainDataVector.clear();
16177  Utilities->CallLogPop(839);
16178  return(false);
16179  }
16180  else
16181  {
16182  if(SetDataAndCheckLocations)
16183  {
16184  if(!(Track->TimetabledLocationNameAllocated(4, ForwardEntryPtr->LocationName)))
16185  {
16186  SecondPassMessage(GiveMessages, "Error in timetable - can't find timetabled location '" + ForwardEntryPtr->LocationName + "' in railway - perhaps there are concourses without platforms?");
16187  TrainDataVector.clear();
16188  Utilities->CallLogPop(849);
16189  return(false);
16190  }
16191  if(!(Track->OneNamedLocationElementAtLocation(0, ForwardEntryPtr->LocationName)))
16192  {
16193  SecondPassMessage(GiveMessages, "Error in timetable - can't find any named location elements at '" + ForwardEntryPtr->LocationName + "' - perhaps there are concourses without platforms?");
16194  TrainDataVector.clear();
16195  Utilities->CallLogPop(850);
16196  return(false);
16197  }
16198 //determine whether LocationName is a station or non-station
16199  bool StationLocation = false;
16200  for(TTrack::TTrackVectorIterator TEIt = Track->InactiveTrackVector.begin(); TEIt != Track->InactiveTrackVector.end(); TEIt++)
16201  {
16202  if(TEIt->LocationName == ForwardEntryPtr->LocationName)
16203  {
16204  if(TEIt->TrackType != NamedNonStationLocation)
16205  {
16206  StationLocation = true;
16207  }
16208  }
16209  }
16210  if(StationLocation)
16211  {
16212  if(!(Track->OneStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
16213  {
16214  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
16215  TrainDataVector.clear();
16216  Utilities->CallLogPop(846);
16217  return(false);
16218  }
16219  }
16220  else
16221  {
16222  if(!(Track->OneNonStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
16223  {
16224  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
16225  TrainDataVector.clear();
16226  Utilities->CallLogPop(2660);
16227  return(false);
16228  }
16229  }
16230  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16231  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16232  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16233  {
16234  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16235  }
16236  }
16237  // NB: May not be set if main train is a service continuation without a description, if so can't do much about it but doesn't affect operation, just the train information display
16238  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16239  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16240  }
16241  }
16242  if(ForwardEntryPtr->Command == "Sns")
16243  {
16244  if(ReverseEntryPtr->Command != "Fns")
16245  {
16246  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fns' event for the 'Sns' train whose headcode is " +
16247  MainHeadCode + " and is formed from a service with headcode " + OtherHeadCode);
16248  TrainDataVector.clear();
16249  Utilities->CallLogPop(531);
16250  return(false);
16251  }
16252  }
16253  if(ForwardEntryPtr->Command == "Fns")
16254  {
16255  if(ReverseEntryPtr->Command != "Sns")
16256  {
16257  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns' event for the train whose headcode is " + MainHeadCode +
16258  " and forms a new service with headcode " + OtherHeadCode);
16259  TrainDataVector.clear();
16260  Utilities->CallLogPop(840);
16261  return(false);
16262  }
16263  else
16264  {
16265  if(SetDataAndCheckLocations)
16266  {
16267  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16268  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16269  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16270  {
16271  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16272  }
16273  }
16274  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16275  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16276  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16277  }
16278  }
16279  if(ForwardEntryPtr->Command == "jbo")
16280  {
16281  if(ReverseEntryPtr->Command != "Fjo")
16282  {
16283  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fjo' event for the train whose headcode is " + MainHeadCode +
16284  " and is joined by a train with headcode " + OtherHeadCode);
16285  TrainDataVector.clear();
16286  Utilities->CallLogPop(841);
16287  return(false);
16288  }
16289  }
16290  if(ForwardEntryPtr->Command == "Fjo")
16291  {
16292  if(ReverseEntryPtr->Command != "jbo")
16293  {
16294  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'jbo' event for the train whose headcode is " + MainHeadCode +
16295  " and joins a train with headcode " + OtherHeadCode);
16296  TrainDataVector.clear();
16297  Utilities->CallLogPop(532);
16298  return(false);
16299  }
16300  else
16301  {
16302  if(SetDataAndCheckLocations)
16303  {
16304  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16305  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16306  }
16307 /*
16308  if((MainTrainDataPtr->MaxRunningSpeed > 5) && (MainTrainDataPtr->MaxRunningSpeed < OtherTrainDataPtr->MaxRunningSpeed))
16309  {
16310  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed; //this should only be set when the trains join
16311  } not when internal timetable being compiled. Dropped at v2.15.0 when Brent Mackie reported error on 18/02/23 via discord
16312 
16313  //added test for > 5 [5 used instead of 0 because of possible floating point errors - though unlikely] above at v1.3.1 because the train will have a
16314  //zero MaxRunningSpeed if it continues from another service - its max speed is set when it takes over from the other service
16315  //notified of this problem by Ian Walker in his email of 25/03/13. Probably redundant anyway because the max speed is reduced at the changeover if the
16316  //'joined by' train's max speed is less.
16317 */
16318  }
16319  }
16320  if(ForwardShuttleStart)
16321  // (ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"))
16322  {
16323  if(!ReverseShuttleFinish)
16324  // (ReverseEntryPtr->Command != "Fns-sh") && (ReverseEntryPtr->Command != "Frh-sh"))
16325  {
16326  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + MainHeadCode +
16327  " from train whose headcode is " + OtherHeadCode + ", has to be Fns-sh, Frh-sh");
16328  TrainDataVector.clear();
16329  Utilities->CallLogPop(1056);
16330  return(false);
16331  }
16332  }
16333  if(ReverseShuttleStart)
16334  // (ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"))
16335  {
16336  if(!ForwardShuttleFinish)
16337  // (ForwardEntryPtr->Command != "Fns-sh") && (ForwardEntryPtr->Command != "Frh-sh"))
16338  {
16339  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + OtherHeadCode +
16340  " from train whose headcode is " + MainHeadCode + ", has to be Fns-sh, Frh-sh");
16341  TrainDataVector.clear();
16342  Utilities->CallLogPop(1057);
16343  return(false);
16344  }
16345  else
16346  {
16347  if(SetDataAndCheckLocations)
16348  {
16349  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16350  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16351  }
16352 /* don't need LinkedTrainEntryPtr for 'OtherTrain' & don't need data transfer as this is done in the
16353  non-repeating link for Sns-sh & is provided at the outset for Snt-sh
16354 */
16355  }
16356  }
16357  // check repeat information consistent if present
16358  // note that won't be affected by the non-repeating shuttle links as these are in NonRepeatingShuttleLinkHeadCode
16359  // and those not accessed here
16360 
16361  // still need to check the non-repeating links and that they have no repeats - do that outside this function
16362  bool MainRepeat = false, OtherRepeat = false;
16363  TActionVectorEntry MainRepeatEntry, OtherRepeatEntry;
16364 
16365  if(MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
16366  {
16367  MainRepeat = true;
16368  MainRepeatEntry = MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1);
16369  }
16370  if(OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
16371  {
16372  OtherRepeat = true;
16373  OtherRepeatEntry = OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1);
16374  }
16375  if((MainRepeat && !OtherRepeat) || (!MainRepeat && OtherRepeat))
16376  {
16377  SecondPassMessage(GiveMessages, "Error in timetable - only one repeat is provided for the train whose headcode is " + MainHeadCode +
16378  " and the associated train with headcode " + OtherHeadCode);
16379  TrainDataVector.clear();
16380  Utilities->CallLogPop(844);
16381  return(false);
16382  }
16383  if(MainRepeat && OtherRepeat)
16384  {
16385  if((MainRepeatEntry.EventTime != OtherRepeatEntry.EventTime) || (MainRepeatEntry.RearStartOrRepeatMins != OtherRepeatEntry.RearStartOrRepeatMins) ||
16386  (MainRepeatEntry.FrontStartOrRepeatDigits != OtherRepeatEntry.FrontStartOrRepeatDigits) ||
16387  (MainRepeatEntry.NumberOfRepeats != OtherRepeatEntry.NumberOfRepeats))
16388  {
16389  SecondPassMessage(GiveMessages, "Error in timetable - repeat items don't correspond for the train whose headcode is " + MainHeadCode +
16390  " and the associated train with headcode " + OtherHeadCode);
16391  TrainDataVector.clear();
16392  Utilities->CallLogPop(845);
16393  return(false);
16394  }
16395  }
16396  Utilities->CallLogPop(863);
16397  return(true);
16398 }
16399 
16400 // ---------------------------------------------------------------------------
16401 
16402 void TTrainController::StripSpaces(int Caller, AnsiString &Input)
16403 // strip both leading and trailing spaces at ends of text and spaces before and after all commas and semicolons within the text
16404 {
16405  // strip spaces from extreme ends of input
16406  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripSpaces," + AnsiString(Input));
16407  if(Input == "")
16408  {
16409  Utilities->CallLogPop(856);
16410  return;
16411  }
16412  while(Input[1] == ' ')
16413  {
16414  if(Input.Length() > 1)
16415  {
16416  Input = Input.SubString(2, Input.Length() - 1);
16417  }
16418  else
16419  {
16420  Input = "";
16421  Utilities->CallLogPop(857);
16422  return;
16423  }
16424  }
16425  if(Input == "")
16426  {
16427  Utilities->CallLogPop(858);
16428  return;
16429  }
16430  while(Input[Input.Length()] == ' ')
16431  {
16432  if(Input.Length() > 1)
16433  {
16434  Input = Input.SubString(1, Input.Length() - 1);
16435  }
16436  else
16437  {
16438  Input = "";
16439  Utilities->CallLogPop(859);
16440  return;
16441  }
16442  }
16443  // now strip spaces immediately after all commas and semicolons within the text
16444  AnsiString Output = "";
16445  bool DelimiterFound = false;
16446 
16447  for(int x = 1; x < Input.Length() + 1; x++)
16448  {
16449  if(DelimiterFound)
16450  {
16451  if(Input[x] == ' ')
16452  {
16453  continue;
16454  }
16455  }
16456  if((Input[x] != ',') && (Input[x] != ';'))
16457  {
16458  DelimiterFound = false;
16459  Output = Output + Input[x];
16460  }
16461  else
16462  {
16463  DelimiterFound = true;
16464  Output = Output + Input[x];
16465  }
16466  }
16467  if(Output == "")
16468  {
16469  Input = "";
16470  Utilities->CallLogPop(860);
16471  return;
16472  }
16473  // now strip spaces immediately before all commas and semicolons within the text
16474  Input = Output;
16475  Output = "";
16476  DelimiterFound = false;
16477  for(int x = Input.Length(); x > 0; x--)
16478  {
16479  if(DelimiterFound)
16480  {
16481  if(Input[x] == ' ')
16482  {
16483  continue;
16484  }
16485  }
16486  if((Input[x] != ',') && (Input[x] != ';'))
16487  {
16488  DelimiterFound = false;
16489  Output = AnsiString(Input[x]) + Output;
16490  }
16491  else
16492  {
16493  DelimiterFound = true;
16494  Output = AnsiString(Input[x]) + Output;
16495  }
16496  }
16497  Input = Output;
16498  Utilities->CallLogPop(861);
16499 }
16500 
16501 // ---------------------------------------------------------------------------
16502 
16503 bool TTrainController::IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName) //this is the original version re-instated at v2.15.0 Beta6
16504 // checks if an Snt or Snt-sh entry with zero starting speed is followed (somewhere, not necessarily immediately) by a TimeLoc & has the same LocationName
16505 // and if so returns true. Also returns true for Snt, not Snt-sh, if at least 1 start element is a location & the entry is either
16506 // a signaller control entry & speed is zero or it is followed immediately by Frh, Fjo, Fns or F-nshs (allows empty stock pickup).
16507 // Always return false for entry at a continuation (may be named but not a stop location). Note that no successor validity checks
16508 // are done in this function, they must be done elsewhere.
16509 //a starting speed > 0 always returns false
16510 {
16511  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsSNTEntryLocated," + AnsiString(TDEntry.HeadCode));
16512  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
16513  LocationName = "";
16514  if(TDEntry.StartSpeed > 0)
16515  {
16516  Utilities->CallLogPop(1784);
16517  return(false);
16518  }
16519  if((AVEntry0.Command != "Snt") && (AVEntry0.Command != "Snt-sh"))
16520  {
16521  throw Exception("Error, first event not 'Snt' or 'Snt-sh' in IsSNTEntryLocated");
16522  }
16524  {
16525  Utilities->CallLogPop(852);
16526  return(false);
16527  }
16528  AnsiString LocRear = Track->TrackElementAt(507, AVEntry0.RearStartOrRepeatMins).ActiveTrackElementName;
16529  AnsiString LocFront = Track->TrackElementAt(508, AVEntry0.FrontStartOrRepeatDigits).ActiveTrackElementName;
16530 
16531  if(LocRear != "")
16532  {
16533  LocationName = LocRear;
16534  }
16535  else
16536  {
16537  LocationName = LocFront;
16538  }
16539  if(LocationName == "")
16540  {
16541  Utilities->CallLogPop(1036);
16542  return(false);
16543  }
16544  if(AVEntry0.SignallerControl)
16545  {
16546  Utilities->CallLogPop(1773);
16547  return(true);
16548  }
16549 // here if not a signaller start entry so must be at least one more entry, and it must be at the same location as the Snt to be located
16550 
16551 //Ok Not ok continue
16552 
16553 //Frh if Snt Frh-sh cdt
16554 //Fns if Snt Fns-sh fsp or rsp
16555 //Fjo if Snt TimeTimeLoc jbo
16556 //F-nshs if Snt pas dsc or cms
16557 //TimeLoc dep Fer
16558 // TimeLoc arr
16559 
16560  for(unsigned int y = 0; y < TDEntry.ActionVector.size(); y++)
16561  {
16562  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
16563  if(((AVEntry.Command == "Frh") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "Fns")) && (AVEntry0.Command == "Snt")) // added Fjo at v2.0.0 for empty stock
16564  {
16565  Utilities->CallLogPop(1037);
16566  return(true);
16567  }
16568  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName == LocationName)) //will be a departure if same name- times not set yet so can't use them to confirm
16569  {
16570  Utilities->CallLogPop(2442);
16571  return(true);
16572  }
16573  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName != LocationName)) //arrival, not located
16574  {
16575  Utilities->CallLogPop(2438);
16576  return(false);
16577  }
16578  if((AVEntry.Command == "Fer") || (AVEntry.Command == "pas") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh") || (AVEntry.FormatType == TimeTimeLoc))
16579  {
16580  Utilities->CallLogPop(854);
16581  return(false);
16582  }
16583  if((AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "jbo"))
16584  {
16585  continue;
16586  }
16587  }
16588  Utilities->CallLogPop(855);
16589  return(false);
16590 
16591 }
16592 
16593 // ---------------------------------------------------------------------------
16594 
16595 bool TTrainController::CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
16596 {
16597  // checks that the new train start elements are valid - both exist & are connected, and that not
16598  // attempting to start on a diverging leg (i.e. one segment on points & other on element connected to diverging leg) <--dropped at v2.18.0
16599  // & not starting with front on a continuation
16600  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartPositionValidity," + RearElementStr + "," + FrontElementStr);
16601  int RearPosition = 0, FrontPosition = 0; // RearExitPos = 0; //dropped at v2.18.0
16602 
16603  RearPosition = Track->GetTrackVectorPositionFromString(5, RearElementStr, GiveMessages);
16604  if(RearPosition < 0)
16605  // error message given in GetTrackVectorPositionFromString
16606  {
16607  Utilities->CallLogPop(759);
16608  return(false);
16609  }
16610  FrontPosition = Track->GetTrackVectorPositionFromString(6, FrontElementStr, GiveMessages);
16611  if(FrontPosition < 0)
16612  // error message given in GetTrackVectorPositionFromString
16613  {
16614  Utilities->CallLogPop(760);
16615  return(false);
16616  }
16617  TTrackElement RearTrackElement = Track->TrackElementAt(490, RearPosition);
16618  TTrackElement FrontTrackElement = Track->TrackElementAt(491, FrontPosition);
16619 // TTrackType RearType = RearTrackElement.TrackType; //dropped at v2.18.0
16620  TTrackType FrontType = FrontTrackElement.TrackType;
16621 
16622  // check front & rear connected
16623  for(int x = 0; x < 4; x++)
16624  {
16625  if(RearTrackElement.Conn[x] == FrontPosition)
16626  {
16627 // RearExitPos = x; //dropped at v2.18.0
16628  break;
16629  }
16630  if(x == 3) //if it gets here & not already found then not connected
16631  {
16632  TimetableMessage(GiveMessages, "Front element: " + FrontTrackElement.ElementID + " not linked to rear element: " + RearTrackElement.ElementID);
16633  Utilities->CallLogPop(762);
16634  return(false);
16635  }
16636  }
16637  // check not starting with front on a continuation
16638  if(FrontType == Continuation)
16639  {
16640  TimetableMessage(GiveMessages, "Front of train attempting to start on a continuation at: " + FrontElementStr);
16641  Utilities->CallLogPop(937);
16642  return(false);
16643  }
16644  // check not starting on a level crossing
16645  if(Track->IsLCAtHV(43, FrontTrackElement.HLoc, FrontTrackElement.VLoc))
16646  {
16647  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + FrontElementStr);
16648  Utilities->CallLogPop(1951);
16649  return(false);
16650  }
16651  if(Track->IsLCAtHV(44, RearTrackElement.HLoc, RearTrackElement.VLoc))
16652  {
16653  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + RearElementStr);
16654  Utilities->CallLogPop(1952);
16655  return(false);
16656  }
16657  // check if trying to start on diverging leg of points - allowed at v2.18.0, checks done during operation
16658 /*
16659  if((RearType == Points) && (RearExitPos == 3))
16660  {
16661  TimetableMessage(GiveMessages, "Front of train attempting to start on element connected to diverging points at: " + RearElementStr);
16662  Utilities->CallLogPop(936);
16663  return(false);
16664  }
16665 
16666  if((FrontType == Points) && (RearTrackElement.ConnLinkPos[RearExitPos] == 3))
16667  {
16668  TimetableMessage(GiveMessages, "Rear of train attempting to start on element connected to diverging points at: " + FrontElementStr);
16669  Utilities->CallLogPop(1808);
16670  return(false);
16671  }
16672 */
16673  Utilities->CallLogPop(905);
16674  return(true);
16675 }
16676 
16677 // ---------------------------------------------------------------------------
16678 
16679 bool TTrainController::CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
16680 // Rear & front element validity already checked in CheckStartPositionValidity
16681 // This checks for points in correct orientation, no train at start position and not starting on a locked route
16682 {
16683  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartAllowable," + AnsiString(RearPosition) + "," +
16684  AnsiString(RearExitPos));
16685  TTrackElement RearTrackElement = Track->TrackElementAt(517, RearPosition);
16686 
16687  if(RearTrackElement.TrackType == Continuation)
16688  {
16689  EventType = FailTrainEntry;
16690  }
16691  else
16692  {
16693  EventType = FailCreateTrain;
16694  }
16695  int FrontPosition = RearTrackElement.Conn[RearExitPos];
16696  TTrackElement FrontTrackElement = Track->TrackElementAt(798, FrontPosition);
16697  int FrontEntryPos = RearTrackElement.ConnLinkPos[RearExitPos];
16698  TTrackType RearType = RearTrackElement.TrackType;
16699  TTrackType FrontType = FrontTrackElement.TrackType;
16700  AnsiString RearName, FrontName;
16701 
16702  if(RearTrackElement.ActiveTrackElementName != "")
16703  {
16704  RearName = RearTrackElement.ActiveTrackElementName;
16705  }
16706  else
16707  {
16708  RearName = RearTrackElement.ElementID;
16709  }
16710  if(FrontTrackElement.ActiveTrackElementName != "")
16711  {
16712  FrontName = FrontTrackElement.ActiveTrackElementName;
16713  }
16714  else
16715  {
16716  FrontName = FrontTrackElement.ElementID;
16717  }
16718  TPrefDirElement PrefDirElement; // needed for next function but not used
16719  int LockedVectorNumber; // needed for next function but not used
16720 
16721  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(12, FrontPosition, FrontEntryPos, PrefDirElement, LockedVectorNumber))
16722  {
16723  if(ReportFlag)
16724  {
16725  if(EventType == FailCreateTrain)
16726  {
16727  EventType = FailCreateLockedRoute;
16728  }
16729  else
16730  {
16731  EventType = FailEnterLockedRoute;
16732  }
16733  LogActionError(47, HeadCode, "", EventType, FrontName);
16734  }
16735  Utilities->CallLogPop(940);
16736  return(false);
16737  }
16738  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(13, RearPosition, RearExitPos, PrefDirElement, LockedVectorNumber))
16739  {
16740  if(ReportFlag)
16741  {
16742  if(EventType == FailCreateTrain)
16743  {
16744  EventType = FailCreateLockedRoute;
16745  }
16746  else
16747  {
16748  EventType = FailEnterLockedRoute;
16749  }
16750  LogActionError(48, HeadCode, "", EventType, RearName);
16751  }
16752  Utilities->CallLogPop(1809);
16753  return(false);
16754  }
16755  if((RearType != Bridge) && (RearTrackElement.TrainIDOnElement > -1))
16756  {
16757  if(ReportFlag)
16758  {
16759  LogActionError(27, HeadCode, "", EventType, RearName);
16760  }
16761  Utilities->CallLogPop(1810);
16762  return(false);
16763  }
16764  if((FrontType != Bridge) && (FrontTrackElement.TrainIDOnElement > -1))
16765  {
16766  if(ReportFlag)
16767  {
16768  if(EventType == FailCreateTrain)
16769  {
16770  LogActionError(28, HeadCode, "", EventType, FrontName);
16771  }
16772  else
16773  {
16774  LogActionError(43, HeadCode, "", EventType, RearName);
16775  }
16776  }
16777  Utilities->CallLogPop(941);
16778  return(false);
16779  }
16780  if(RearType == Bridge)
16781  {
16782  if((RearExitPos > 1) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16783  {
16784  if(ReportFlag)
16785  {
16786  LogActionError(29, HeadCode, "", EventType, RearName);
16787  }
16788  Utilities->CallLogPop(942);
16789  return(false);
16790  }
16791  if((RearExitPos < 2) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16792  {
16793  if(ReportFlag)
16794  {
16795  LogActionError(30, HeadCode, "", EventType, RearName);
16796  }
16797  Utilities->CallLogPop(943);
16798  return(false);
16799  }
16800  }
16801  if(FrontType == Bridge)
16802  {
16803  if((FrontEntryPos > 1) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16804  {
16805  if(ReportFlag)
16806  {
16807  if(EventType == FailCreateTrain)
16808  {
16809  LogActionError(31, HeadCode, "", EventType, FrontName);
16810  }
16811  else
16812  {
16813  LogActionError(44, HeadCode, "", EventType, RearName);
16814  }
16815  }
16816  Utilities->CallLogPop(944);
16817  return(false);
16818  }
16819  if((FrontEntryPos < 2) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16820  {
16821  if(ReportFlag)
16822  {
16823  if(EventType == FailCreateTrain)
16824  {
16825  LogActionError(45, HeadCode, "", EventType, FrontName);
16826  }
16827  else
16828  {
16829  LogActionError(46, HeadCode, "", EventType, RearName);
16830  }
16831  }
16832  Utilities->CallLogPop(945);
16833  return(false);
16834  }
16835  }
16836  EventType = FailCreatePoints; //modified at v2.18.0 so only fails if starting positions conflict with point attribute
16837  if(RearType == Points)
16838  {
16839  if(((RearTrackElement.Attribute == 1) && (RearExitPos == 1)) || ((RearTrackElement.Attribute == 0) && (RearExitPos == 3)))
16840  {
16841  if(ReportFlag)
16842  {
16843  LogActionError(33, HeadCode, "", FailCreatePoints, RearName);
16844  StopTTClockMessage(157, HeadCode + " can't be created, points set wrongly at " + RearName);
16845  }
16846  Utilities->CallLogPop(933);
16847  return(false);
16848  }
16849  }
16850  if(FrontType == Points)
16851  {
16852  if(((FrontTrackElement.Attribute == 1) && (FrontEntryPos == 1)) || ((FrontTrackElement.Attribute == 0) && (FrontEntryPos == 3)))
16853  {
16854  if(ReportFlag)
16855  {
16856  LogActionError(34, HeadCode, "", FailCreatePoints, FrontName);
16857  StopTTClockMessage(158, HeadCode + " can't be created, points set wrongly at " + RearName);
16858  }
16859  Utilities->CallLogPop(934);
16860  return(false);
16861  }
16862  }
16863 
16864  //this section added at v2.9.1 to prevent entry for a train when there's a route set against it
16865  int HLoc = Track->TrackElementAt(1027, RearPosition).HLoc;
16866  int VLoc = Track->TrackElementAt(1028, RearPosition).VLoc;
16867  int ELink = Track->TrackElementAt(1029, RearPosition).Link[RearExitPos]; //if route entry corresponds to RearExitPos then it's set against the train
16868  int RouteNumber; //not used
16869  if(Track->TrackElementAt(1030, RearPosition).TrackType == Continuation)
16870  {
16871  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(8, HLoc, VLoc, ELink, RouteNumber))
16872  {
16873  EventType = FailEntryRouteSetAgainst;
16874  if(ReportFlag)
16875  {
16876  LogActionError(63, HeadCode, "", EventType, RearName);
16877  }
16878  Utilities->CallLogPop(2317);
16879  return(false);
16880  }
16881  }
16882  Utilities->CallLogPop(939);
16883  return(true);
16884 }
16885 
16886 // ---------------------------------------------------------------------------
16887 
16888 AnsiString TTrainController::GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
16889 {
16890  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatHeadCode," + BaseHeadCode + "," + AnsiString(RepeatNumber) +
16891  "," + AnsiString(IncDigits));
16892  if(!Last2CharactersBothDigits(1, BaseHeadCode) && (IncDigits > 0))
16893  {
16894  throw Exception("Error, last 2 characters not both digits and IncDigits > 0 in GetRepeatHeadCode");
16895  }
16896  if(!Last2CharactersBothDigits(2, BaseHeadCode))
16897  {
16898  Utilities->CallLogPop(1893);
16899  return(BaseHeadCode);
16900  }
16901  int BaseDigits = BaseHeadCode.SubString(3, 2).ToInt();
16902  int NextRepeatDigits = BaseDigits + (IncDigits * RepeatNumber);
16903 
16904  while(NextRepeatDigits >= 100)
16905  {
16906  NextRepeatDigits -= 100; // rolls over after 99
16907  }
16908  AnsiString NextRepeatDigitsStr = AnsiString(NextRepeatDigits);
16909 
16910  if(NextRepeatDigitsStr.Length() < 2)
16911  {
16912  NextRepeatDigitsStr = AnsiString('0') + NextRepeatDigitsStr;
16913  }
16914  AnsiString NextRepeatHeadCode = BaseHeadCode.SubString(1, 2) + NextRepeatDigitsStr;
16915 
16916  Utilities->CallLogPop(1365);
16917  return(NextRepeatHeadCode);
16918 }
16919 
16920 // ---------------------------------------------------------------------------
16921 
16922 TDateTime TTrainController::GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
16923 {
16924  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatTime," + AnsiString(double(BasicTime)) + "," +
16925  AnsiString(RepeatNumber) + "," + AnsiString(IncMinutes));
16926  TDateTime NextRepeatTime = BasicTime + TDateTime(((double)(RepeatNumber * IncMinutes)) / 1440.0); // 1440 = no. of minutes in 24h
16927  Utilities->CallLogPop(1366);
16928  return(NextRepeatTime);
16929 }
16930 
16931 // ---------------------------------------------------------------------------
16932 
16933 bool TTrainController::CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
16934 // For success the ForwardEventTime + repeat time should == ReverseEventTime (allow 10secs either way since converting to doubles)
16935 {
16936  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleRepeatTime," + AnsiString(double(ForwardEventTime)) + "," +
16937  AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes));
16938  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16939  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16940  int RepeatSecs = RepeatMinutes * 60;
16941 
16942  if((ForwardSecs > (ReverseSecs - RepeatSecs + 10)) || (ForwardSecs < (ReverseSecs - RepeatSecs - 10)))
16943  {
16944  Utilities->CallLogPop(1367);
16945  return(false);
16946  }
16947  else
16948  {
16949  Utilities->CallLogPop(1368);
16950  return(true);
16951  }
16952 }
16953 
16954 // ---------------------------------------------------------------------------
16955 
16956 bool TTrainController::CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
16957 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent, set links if SetDataAndCheckLocations true
16958 
16959 /* Double crosslink (shuttle) table:
16960 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
16961  Code ShuttleLink- EntryPtr ShuttleLink-
16962  HeadCode EntryPtr
16963 
16964 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
16965 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
16966 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
16967 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
16968 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
16969 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
16970 
16971 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
16972 */
16973 
16974 {
16975  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinksAndSetData," + MainHeadCode + "," +
16976  NonRepeatingHeadCode);
16977  int ForwardCount = 0;
16978  int ReverseCount = 0;
16979  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
16980  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
16981  // Forward corresponds to Main, Reverse to Other
16982  TTrainDataEntry *MainTrainDataPtr = 0;
16983  TTrainDataEntry *OtherTrainDataPtr = 0;
16984 
16985  // forward check
16986  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16987  {
16988  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16989  if(TDEntry.HeadCode == MainHeadCode)
16990  {
16991  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16992  {
16993  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16994  if(AVEntry.NonRepeatingShuttleLinkHeadCode == NonRepeatingHeadCode)
16995  {
16996  MainTrainDataPtr = &TrainDataVector.at(x);
16997  ForwardEntryPtr = &AVEntry;
16998  ForwardCount++;
16999  ForwardTDVectorNumber = x;
17000  }
17001  }
17002  }
17003  }
17004  if(ForwardCount == 0)
17005  // this is an exception because the headcodes are selected in the same order as the forward check
17006  {
17007  throw Exception("Error, ForwardCount == 0 in CheckNonRepeatingShuttleLinksAndSetData after called with found values");
17008  }
17009  if(ForwardCount > 1)
17010  {
17011  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + NonRepeatingHeadCode + " from a train whose headcode is " +
17012  MainHeadCode);
17013  TrainDataVector.clear();
17014  Utilities->CallLogPop(1061);
17015  return(false);
17016  }
17017  // reverse check
17018  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17019  {
17020  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
17021  if(TDEntry.HeadCode == NonRepeatingHeadCode)
17022  {
17023  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
17024  {
17025  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
17026  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
17027  {
17028  OtherTrainDataPtr = &TrainDataVector.at(x);
17029  ReverseCount++;
17030  ReverseEntryPtr = &AVEntry;
17031  ReverseTDVectorNumber = x;
17032  }
17033  }
17034  }
17035  }
17036 
17037  if(ReverseCount == 0)
17038  {
17039  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + NonRepeatingHeadCode);
17040  TrainDataVector.clear();
17041  Utilities->CallLogPop(1062);
17042  return(false);
17043  }
17044  if(ReverseCount > 1)
17045  {
17046  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
17047  NonRepeatingHeadCode);
17048  TrainDataVector.clear();
17049  Utilities->CallLogPop(1063);
17050  return(false);
17051  }
17052  if(((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh")) && (MainTrainDataPtr->ActionVector.back().FormatType == Repeat))
17053  {
17054  SecondPassMessage(GiveMessages, "Error in timetable - shuttle connecting train " + MainHeadCode + " shouldn't have a repeat");
17055  TrainDataVector.clear();
17056  Utilities->CallLogPop(1064);
17057  return(false);
17058  }
17059  if((ForwardEntryPtr->Command != "F-nshs") && (ForwardEntryPtr->Command != "Sns-fsh") && (MainTrainDataPtr->ActionVector.back().FormatType != Repeat))
17060  {
17061  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat item");
17062  TrainDataVector.clear();
17063  Utilities->CallLogPop(1065);
17064  return(false);
17065  }
17066  if(SetDataAndCheckLocations)
17067  {
17068  if(ForwardEntryPtr->LocationName == "")
17069  {
17070  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
17071  ". One or other service does not have a location set");
17072  TrainDataVector.clear();
17073  Utilities->CallLogPop(1066);
17074  return(false);
17075  }
17076  if(ReverseEntryPtr->LocationName == "")
17077  {
17078  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
17079  ". One or other service does not have a location set");
17080  TrainDataVector.clear();
17081  Utilities->CallLogPop(1067);
17082  return(false);
17083  }
17084  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
17085  {
17086  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + NonRepeatingHeadCode +
17087  " is at a different location to the referencing train " + MainHeadCode);
17088  TrainDataVector.clear();
17089  Utilities->CallLogPop(1068);
17090  return(false);
17091  }
17092  }
17093  if(ForwardEntryPtr->Command == "F-nshs")
17094  // i.e. the non repeating link into the shuttle service
17095  {
17096  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
17097  {
17098  SecondPassMessage(GiveMessages, "Error in timetable - shuttle in-link service " + MainHeadCode +
17099  " finish time not consistent with start time of shuttle service " + NonRepeatingHeadCode);
17100  TrainDataVector.clear();
17101  Utilities->CallLogPop(1069);
17102  return(false);
17103  }
17104  }
17105  if(ForwardEntryPtr->Command == "Fns-sh")
17106  // i.e. the non repeating link out from the shuttle service
17107  {
17108  if(!CheckNonRepeatingShuttleLinkTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime,
17109  MainTrainDataPtr->ActionVector.back().RearStartOrRepeatMins, MainTrainDataPtr->ActionVector.back().NumberOfRepeats))
17110  {
17111  SecondPassMessage(GiveMessages, "Error in timetable - service " + NonRepeatingHeadCode + ", which links out from shuttle service " + MainHeadCode +
17112  ", has the wrong start time. It should correspond to the finish time of the last shuttle.");
17113  TrainDataVector.clear();
17114  Utilities->CallLogPop(1070);
17115  return(false);
17116  }
17117  }
17118  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))
17119  // i.e. a non repeating link to or from the shuttle service
17120  {
17121  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
17122  {
17123  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode +
17124  " appears in the same sequence as the corresponding shuttle service " + MainHeadCode);
17125  TrainDataVector.clear();
17126  Utilities->CallLogPop(1071);
17127  return(false);
17128  }
17129  }
17130 /* it's allowed to have a different description
17131  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))//i.e. a non repeating link to or from the shuttle service
17132  {
17133  if((MainTrainDataPtr->Description != "") && (OtherTrainDataPtr->Description != "") && (MainTrainDataPtr->Description != OtherTrainDataPtr->Description))
17134  {
17135  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode + " has a different description to the corresponding shuttle service " + MainHeadCode);
17136  TrainDataVector.clear();
17137  Utilities->CallLogPop(1072);
17138  return false;
17139  }
17140  }
17141 */
17142  if(ForwardEntryPtr->Command == "Sns-sh")
17143  {
17144  if(ReverseEntryPtr->Command != "F-nshs")
17145  {
17146  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'F-nshs' event for the 'Sns-sh' train whose headcode is " +
17147  MainHeadCode + " and is a new shuttle service formed from the service with headcode " + NonRepeatingHeadCode);
17148  TrainDataVector.clear();
17149  Utilities->CallLogPop(1073);
17150  return(false);
17151  }
17152  }
17153  if(ForwardEntryPtr->Command == "F-nshs")
17154  {
17155  if(ReverseEntryPtr->Command != "Sns-sh")
17156  {
17157  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns-sh' event for the 'F-nshs' train whose headcode is " +
17158  MainHeadCode + " and forms a new shuttle service with headcode " + NonRepeatingHeadCode);
17159  TrainDataVector.clear();
17160  Utilities->CallLogPop(1074);
17161  return(false);
17162  }
17163  else
17164  {
17165  if(SetDataAndCheckLocations)
17166  {
17167  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
17168  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
17169  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
17170  {
17171  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
17172  }
17173  }
17174  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17175  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
17176  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17177  }
17178  }
17179  if(ForwardEntryPtr->Command == "Sns-fsh")
17180  {
17181  if(ReverseEntryPtr->Command != "Fns-sh")
17182  {
17183  SecondPassMessage(GiveMessages,
17184  "Error in timetable - unable to find a corresponding 'Fns-sh' event for the 'Sns-fsh' non-shuttle service whose headcode is " + MainHeadCode +
17185  " formed from a shuttle service with headcode " + NonRepeatingHeadCode);
17186  TrainDataVector.clear();
17187  Utilities->CallLogPop(1075);
17188  return(false);
17189  }
17190  }
17191  if(ForwardEntryPtr->Command == "Fns-sh")
17192  {
17193  if(ReverseEntryPtr->Command != "Sns-fsh")
17194  {
17195  SecondPassMessage(GiveMessages,
17196  "Error in timetable - unable to find a corresponding 'Sns-fsh' event for the 'Fns-sh' shuttle service whose headcode is " + MainHeadCode +
17197  " and forms a new non-shuttle service with headcode " + NonRepeatingHeadCode);
17198  TrainDataVector.clear();
17199  Utilities->CallLogPop(1076);
17200  return(false);
17201  }
17202  else
17203  {
17204  if(SetDataAndCheckLocations)
17205  {
17206  ForwardEntryPtr->NonRepeatingShuttleLinkEntryPtr = OtherTrainDataPtr;
17207  // links to the non-repeating non-shuttle linked service
17208  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
17209  // needed for creating formatted timetable
17210  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
17211  {
17212  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
17213  }
17214  }
17215  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17216  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
17217  }
17218  }
17219  Utilities->CallLogPop(1077);
17220  return(true);
17221 }
17222 
17223 // ---------------------------------------------------------------------------
17224 
17225 bool TTrainController::CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes, int RepeatNumber)
17226 // Forward train is the finish shuttle entry 'Fns-sh'.
17227 // The Reverse (new non-repeating service) time must == Forward time + (RepeatMins * RepeatNumber) but allow 10 secs either side
17228 {
17229  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinkTime," + AnsiString(double(ForwardEventTime))
17230  + "," + AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes) + "," + AnsiString(RepeatNumber));
17231  int ForwardSecs = int(double(ForwardEventTime) * 86400);
17232  int ReverseSecs = int(double(ReverseEventTime) * 86400);
17233  int RepeatSecs = RepeatMinutes * RepeatNumber * 60;
17234 
17235  if((ReverseSecs > (ForwardSecs + RepeatSecs + 10)) || (ReverseSecs < (ForwardSecs + RepeatSecs - 10)))
17236  {
17237  Utilities->CallLogPop(1369);
17238  return(false);
17239  }
17240  else
17241  {
17242  Utilities->CallLogPop(1370);
17243  return(true);
17244  }
17245 }
17246 
17247 // ---------------------------------------------------------------------------
17248 
17249 bool TTrainController::CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
17250 // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
17251 // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
17252 // don't ever need to and as designed would skip repeats.
17253 
17254 // enter with TDEntry a shuttle start - Snt-sh or Sns-sh
17255 {
17256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleServiceIntegrity," + AnsiString(TDEntryPtr->HeadCode));
17257  if(TDEntryPtr->ActionVector.back().FormatType != Repeat)
17258  {
17259  throw Exception("Error - last entry in " + TDEntryPtr->HeadCode + " service is not a repeat - should have already found this error");
17260  }
17261  TTrainDataEntry *ShuttleStartAddress = TDEntryPtr;
17262  AnsiString OriginalHeadCode = TDEntryPtr->HeadCode;
17263  AnsiString LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
17264 
17265  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
17266  {
17267  SecondPassMessage(GiveMessages, "Error in timetable - last event in shuttle service " + TDEntryPtr->HeadCode + " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
17268  TrainDataVector.clear();
17269  Utilities->CallLogPop(1091);
17270  return(false);
17271  }
17272  while(LastActionCommand == "Fns")
17273  {
17274  TDEntryPtr = (TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr;
17275  LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
17276  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
17277  {
17278  SecondPassMessage(GiveMessages,
17279  "Error in timetable - last event in a continuation shuttle service (i.e links back to a shuttle) whose headcode is " + TDEntryPtr->HeadCode +
17280  " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
17281  TrainDataVector.clear();
17282  Utilities->CallLogPop(1092);
17283  return(false);
17284  }
17285  }
17286  // exit the 'while' with LastActionCommand FSH-XX
17287  if((TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr != ShuttleStartAddress)
17288  {
17289  SecondPassMessage(GiveMessages, "Error in timetable - the event that ends service " + TDEntryPtr->HeadCode +
17290  " is a shuttle finish, but it doesn't link back to the start of the original shuttle starting service " + OriginalHeadCode +
17291  ". The linking of two or more shuttles is not permitted.");
17292  TrainDataVector.clear();
17293  Utilities->CallLogPop(1093);
17294  return(false);
17295  }
17296  Utilities->CallLogPop(1094);
17297  return(true);
17298 }
17299 
17300 // ---------------------------------------------------------------------------
17301 
17302 void TTrainController::TimetableMessage(bool GiveMessages, AnsiString Message)
17303 {
17304  if(!GiveMessages)
17305  {
17306  return;
17307  }
17308  // if(ServiceReference == "") ShowMessage(Message);
17309  if(!CheckHeadCodeValidity(12, false, ServiceReference))
17310  {
17311  ShowMessage(ServiceReference + " (not a valid service ref.): " + Message); //amended at v2.15.1 to give information on 'service' so can find it in the list
17312  }
17313  // changed from above at v2.3.0 as a meaningless value for 'Timetable invalid - unable to find a valid start time on its own line' (uses last entry text)
17314  // false means don't give messages within the function
17315  else
17316  {
17317  ShowMessage("Service " + ServiceReference + ": " + Message);
17318  }
17319 }
17320 
17321 // ---------------------------------------------------------------------------
17322 
17323 void TTrainController::SecondPassMessage(bool GiveMessages, AnsiString Message)
17324 {
17325  if(!GiveMessages)
17326  {
17327  return;
17328  }
17329  ShowMessage(Message);
17330 }
17331 
17332 // ---------------------------------------------------------------------------
17333 
17334 AnsiString TTrainController::MinsToAnsiTime(int Input) //added at v2.15.0
17335 {
17336  TrainController->LogEvent("MinsToAnsiTime");
17337  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",MinsToAnsiTime," + Input);
17338  int Mins = Input, Hrs = 0;
17339  while(Mins > 59)
17340  {
17341  Mins -= 60;
17342  Hrs++;
17343  }
17344  AnsiString AnsiMins = AnsiString(Mins);
17345  if(AnsiMins.Length() == 1)
17346  {
17347  AnsiMins = "0" + AnsiMins;
17348  }
17349  AnsiString AnsiHrs = AnsiString(Hrs);
17350  if(AnsiHrs.Length() == 1)
17351  {
17352  AnsiHrs = "0" + AnsiHrs;
17353  }
17354  Utilities->CallLogPop(2577);
17355  return(AnsiHrs + ':' + AnsiMins);
17356 }
17357 
17358 // $$$$$$$$$$$$$$$$$$$$$$$ End of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$
17359 // ---------------------------------------------------------------------------
17360 
17361 void TTrainController::LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
17362 // FailTrainEntry: 06:00:10 HELD: 2F43 can't enter railway, train obstructing entry position 57-N5
17363 // FailCreateTrain: 06:00:10 HELD: 2F43 can't be created, train obstructing start position 57-N5
17364 // FailCreateLockedRoute: 06:00:10 HELD: 2F43 can't be created on a locked route - start position 57-N5
17365 // FailEnterLockedRoute: 06:00:10 HELD: 2F43 can't enter on a locked route - start position 57-N5
17366 // FailCreatePoints: 06:00:10 HELD: 2F43 can't be created, points set against start position 57-N5
17367 // FailUnexpectedExitRailway: 06:00:10 ERROR: 2F43 left railway unexpectedly at position 57-N5
17368 // FailIncorrectExit: 06:00:10 ERROR: 2F43 left railway at an incorrect exit at position 57-N5
17369 // FailSPAD: 06:00:10 ERROR: 2F43 PASSED SIGNAL AT DANGER at position 57-N5
17370 // FailLockedRoute: 06:00:10 ERROR: SPAD Risk! Signals reset ahead of train, at position 57-N5
17371 // FailLocTooShort: 06:00:10 ERROR: 2F43 failed to split - location too short at Essex Road
17372 // FailSplitDueToOtherTrain: 06:00:10 HELD: 2F43 unable to split - another train is obstructing at Essex Road, please move it if possible
17373 // FailCrashed: 06:00:10: ERROR: 2F43 CRASHED INTO 3F43 at position 46-N7
17374 // FailDerailed: 06:00:10: ERROR: 2F43 DERAILED at position 46-N7
17375 // FailUnexpectedBuffers: 06:00:10: ERROR: 2F43 stopped at buffers unexpectedly at position 46-N7
17376 // FailMissedArrival: 06:00:10: ERROR: 2F43 failed to stop at Essex Road;
17377 // FailMissedSplit: 06:00:10: ERROR: 2F43 failed to split at Essex Road
17378 // FailMissedJBO: 06:00:10: ERROR: 2F43 failed to be joined by join other train at Essex Road
17379 // FailMissedDSC: 06:00:10: ERROR: 2F43 failed to change its description at Essex Road
17380 // FailMissedCMS: 06:00:10: ERROR: 2F43 failed to change its maximum speed at Essex Road
17381 // FailMissedJoinOther: 06:00:10: ERROR: 2F43 failed to join other train at Essex Road
17382 // FailMissedTerminate: 06:00:10: ERROR: 2F43 failed to terminate at Essex Road
17383 // FailMissedNewService: 06:00:10: ERROR: 2F43 failed to form new service at Essex Road
17384 // FailMissedExitRailway: 06:00:10: ERROR: 2F43 failed to exit railway
17385 // FailMissedChangeDirection: 06:00:10: ERROR: 2F43 failed to change direction at Essex Road
17386 // FailMissedPass: 06:00:10: ERROR: 2F43 failed to pass Essex Road
17387 // FailBuffersPreventingStart: 06:00:10: ERROR: 2F43 facing buffers and unable to start at Essex Road
17388 // FailBufferCrash: 06:00:10: ERROR: 2F43 CRASHED INTO BUFFERS at 46-N7
17389 // FailLevelCrossingCrash: 06:00:10: ERROR: 2F43 CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at 46-N7
17390 // RouteForceCancelled: 06:00:10: ERROR: 2F43 forced a route cancellation by occupying it incorrectly at 46-N7
17391 // WaitingForJBO: 06:00:10: WARNING: 2F43 waiting to join 3F43 at Essex Road
17392 // WaitingForFJO: 06:00:10: WARNING: 2F43 waiting to be joined by 3F43 at Essex Road
17393 // FailEntryRouteSetAgainst: 06:00:10: WARNING: 2F43 can't enter railway, route set against it at entry position 57-N5 //added at v2.9.1
17394 // FailNoPowerUnableToDepart: 06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
17395 // FailTrainInFront 06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
17396 
17397 {
17398  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogActionError," + HeadCode + "," + OtherHeadCode + "," +
17399  AnsiString(ActionEventType) + "," + LocationID);
17400  AnsiString BaseLog = "", Prefix = "", ErrorLog = "", WarningStr = "";
17401 
17402  TDateTime ActualTime = TrainController->TTClockTime; //moved from lower down at v2.9.1
17403  AnsiString TimeAndHeadCode = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode; //added at v2.9.1 to give more info to user
17404 
17405  Prefix = " ERROR: ";
17406  if(ActionEventType == FailTrainEntry)
17407  {
17408  Prefix = " HELD: ";
17409  ErrorLog = " can't enter railway, train obstructing entry position ";
17410  WarningStr = " can't enter railway, train obstructing entry position ";
17411  Display->WarningLog(1, TimeAndHeadCode + WarningStr + LocationID);
17412  }
17413  else if(ActionEventType == FailEntryRouteSetAgainst) //added at v2.9.1
17414  {
17415  Prefix = " HELD: ";
17416  ErrorLog = " can't enter railway, route set against it at entry position ";
17417  WarningStr = " can't enter railway, route set against it at entry position ";
17418  Display->WarningLog(10, TimeAndHeadCode + WarningStr + LocationID);
17419  }
17420  else if(ActionEventType == FailCreateTrain)
17421  {
17422  Prefix = " HELD: ";
17423  ErrorLog = " can't be created, train obstructing at ";
17424  WarningStr = " can't be created, train obstructing at ";
17425  Display->WarningLog(2, TimeAndHeadCode + WarningStr + LocationID);
17426  }
17427  else if(ActionEventType == FailCreateLockedRoute)
17428  {
17429  Prefix = " HELD: ";
17430  ErrorLog = " can't be created on a locked route at ";
17431  WarningStr = " can't be created on a locked route at ";
17432  Display->WarningLog(4, TimeAndHeadCode + WarningStr + LocationID);
17433  }
17434  else if(ActionEventType == FailEnterLockedRoute)
17435  {
17436  Prefix = " HELD: ";
17437  ErrorLog = " can't enter on a locked route at ";
17438  WarningStr = " can't enter on a locked route at ";
17439  Display->WarningLog(5, TimeAndHeadCode + WarningStr + LocationID);
17440  }
17441  else if(ActionEventType == FailCreatePoints)
17442  {
17443  Prefix = " HELD: ";
17444  ErrorLog = " can't be created, points set wrongly at ";
17445  WarningStr = " can't be created, points set wrongly at ";
17446  Display->WarningLog(3, TimeAndHeadCode + WarningStr + LocationID);
17447  }
17448  else if(ActionEventType == FailUnexpectedExitRailway)
17449  {
17450  ErrorLog = " left railway unexpectedly at ";
17451  UnexpectedExits++;
17452  }
17453  else if(ActionEventType == FailIncorrectExit)
17454  {
17455  ErrorLog = " left railway at an incorrect exit at ";
17456  IncorrectExits++;
17457  }
17458  else if(ActionEventType == FailLocTooShort)
17459  {
17460  ErrorLog = " failed to split - location too short at ";
17461  WarningStr = " failed to split, location too short at ";
17462  Display->WarningLog(6, TimeAndHeadCode + WarningStr + LocationID);
17463  }
17464  else if(ActionEventType == FailSplitDueToOtherTrain)
17465  {
17466  Prefix = " HELD: ";
17467  ErrorLog = " unable to split - other train obstructing at ";
17468  WarningStr = " unable to split - other train obstructing at ";
17469  Display->WarningLog(7, TimeAndHeadCode + WarningStr + LocationID);
17470  }
17471  else if(ActionEventType == FailUnexpectedBuffers)
17472  {
17473  ErrorLog = " stopped at buffers unexpectedly at position ";
17474  }
17475  else if(ActionEventType == FailMissedArrival)
17476  {
17477  ErrorLog = " failed to stop at ";
17478  MissedStops++;
17479  }
17480  else if(ActionEventType == FailMissedSplit)
17481  {
17482  ErrorLog = " failed to split at ";
17484  }
17485  else if(ActionEventType == FailMissedJBO)
17486  {
17487  ErrorLog = " failed to be joined by other train at ";
17489  }
17490  else if(ActionEventType == FailMissedDSC) //new at v2.15.0
17491  {
17492  ErrorLog = " failed to change its description at ";
17493 // OtherMissedEvents++; shouldn't count
17494  }
17495  else if(ActionEventType == FailMissedCMS) //new at v2.21.0
17496  {
17497  ErrorLog = " failed to change its maximum speed at ";
17498 // OtherMissedEvents++; shouldn't count
17499  }
17500  else if(ActionEventType == FailMissedJoinOther)
17501  {
17502  ErrorLog = " failed to join other train at ";
17504  }
17505  else if(ActionEventType == FailMissedTerminate)
17506  {
17507  ErrorLog = " failed to terminate at ";
17509  }
17510  else if(ActionEventType == FailMissedNewService)
17511  {
17512  ErrorLog = " failed to form new service at ";
17514  }
17515  else if(ActionEventType == FailMissedExitRailway)
17516  {
17517  ErrorLog = " failed to exit railway ";
17519  }
17520  else if(ActionEventType == FailMissedChangeDirection)
17521  {
17522  ErrorLog = " failed to change direction at ";
17523 // OtherMissedEvents++; //dropped at v2.12.0 as cdt shouldn't count
17524  }
17525  else if(ActionEventType == FailMissedPass)
17526  {
17527  ErrorLog = " failed to pass ";
17528 // OtherMissedEvents++; //dropped at v2.12.0 as missed pass shouldn't count
17529  }
17530  else if(ActionEventType == FailBuffersPreventingStart)
17531  {
17532  ErrorLog = " facing buffers and unable to start at ";
17533  }
17534  else if(ActionEventType == FailDerailed)
17535  {
17536  ErrorLog = " DERAILED at position ";
17537  Prefix = " DERAILMENT: ";
17538  Derailments++;
17539  }
17540  else if(ActionEventType == FailBufferCrash)
17541  {
17542  ErrorLog = " CRASHED INTO BUFFERS at ";
17543  Prefix = " CRASH: ";
17544  CrashedTrains++;
17545  }
17546  else if(ActionEventType == FailLevelCrossingCrash)
17547  {
17548  ErrorLog = " CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at ";
17549  Prefix = " CRASH: ";
17550  CrashedTrains++;
17551  }
17552  else if(ActionEventType == FailCrashed)
17553  {
17554  ErrorLog = " CRASHED INTO " + OtherHeadCode + " at position ";
17555  Prefix = " CRASH: ";
17556  CrashedTrains++;
17557  CrashedTrains++;
17558  }
17559  else if(ActionEventType == FailSPAD)
17560  {
17561  ErrorLog = " PASSED SIGNAL AT DANGER at position ";
17562  Prefix = " SPAD: ";
17563  SPADEvents++;
17564  }
17565  else if(ActionEventType == FailLockedRoute)
17566  {
17567  ErrorLog = "Signals reset ahead of train, route cancelled at position ";
17568  Prefix = " SPAD RISK: ";
17569  SPADRisks++;
17570  }
17571  else if(ActionEventType == RouteForceCancelled)
17572  {
17573  ErrorLog = " forced a route cancellation by occupying it incorrectly at ";
17574  }
17575  else if(ActionEventType == WaitingForJBO)
17576  {
17577  Prefix = " WARNING: ";
17578  ErrorLog = " waiting to join " + OtherHeadCode + " at ";
17579  WarningStr = " waiting to join " + OtherHeadCode + " at ";
17580  Display->WarningLog(8, TimeAndHeadCode + WarningStr + LocationID);
17581  }
17582  else if(ActionEventType == WaitingForFJO)
17583  {
17584  Prefix = " WARNING: ";
17585  ErrorLog = " waiting to be joined by " + OtherHeadCode + " at ";
17586  WarningStr = " waiting to be joined by " + OtherHeadCode + " at ";
17587  Display->WarningLog(9, TimeAndHeadCode + WarningStr + LocationID);
17588  }
17589  else if(ActionEventType == FailNoPowerUnableToDepart) //06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
17590  {
17591  Prefix = " WARNING: ";
17592  ErrorLog = " is without power so it can't depart from ";
17593  WarningStr = " is without power so it can't depart from ";
17594  Display->WarningLog(27, TimeAndHeadCode + WarningStr + LocationID);
17595  }
17596  else if(ActionEventType == FailTrainInFront) //06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
17597  {
17598  Prefix = " WARNING: ";
17599  ErrorLog = " can't depart because there is a train in front at ";
17600  WarningStr = " can't depart because there is a train in front at ";
17601  Display->WarningLog(28, TimeAndHeadCode + WarningStr + LocationID);
17602  }
17603 
17604  BaseLog = Utilities->Format96HHMMSS(ActualTime) + Prefix + HeadCode;
17605  PerfLogForm->PerformanceLog(4, BaseLog + ErrorLog + LocationID);
17606  Utilities->CallLogPop(1371);
17607 }
17608 
17609 // ---------------------------------------------------------------------------
17610 
17612 {
17613 /* //for testing purposes
17614  TrainDataEntry
17615  AnsiString HeadCode, Description;//null on creation
17616  int StartSpeed, MaxRunningSpeed;//both kph
17617  int RepeatNumber;
17618  TActionVector ActionVector;
17619  TTrainOperatingDataVector TrainOperatingDataVector;//no of repeats + 1
17620  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; RepeatNumber=0;}
17621 
17622  ActionVectorEntry
17623  TTimetableEntryType FormatType;
17624  TDateTime EventTime, ArrivalTime, DepartureTime;//zeroed on creation so change to -1 as a marker for 'not set'
17625  AnsiString LocationName, Command, OtherHeadCode;//null on creation
17626  TActionVectorEntry *OtherHeadCodeStartingEntryPtr;
17627  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17628  int RepeatNumber;
17629 
17630  TrainOperatingData
17631  int Mass, MaxBrakeRate, PowerAtRail;//kg;m/s/s;W
17632  int TrainID;
17633  TRunningEntry RunningEntry;
17634  TDateTime StartTime;
17635  AnsiString HeadCode;
17636 */
17637  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveTrainDataVectorToFile");
17638  std::ofstream OutFile("TrainData.csv");
17639 
17640  if(OutFile == 0)
17641  {
17642  ShowMessage("Output file TrainData.csv failed to open");
17643  Utilities->CallLogPop(1372);
17644  return;
17645  }
17646  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17647  {
17648  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
17649  OutFile << "HeadCode" << ',' << "Description" << ',' << "StartSpeed" << ',' << "MaxRunningSpeed" << ',' << "NumberOfTrains" << '\n' << '\n';
17650 
17651  OutFile << TDEntry.HeadCode.c_str() << ',' << TDEntry.FixedDescription.c_str() /* name changed at v2.16.1*/
17652  << ',' << TDEntry.StartSpeed << ',' << TDEntry.MaxRunningSpeed << ',' << TDEntry.NumberOfTrains << '\n' << '\n';
17653 
17654  OutFile << ',' << "FormatType" << ',' << "EventTime" << ',' << "ArrivalTime" << ',' << "DepartureTime" << ',' << "LocationName" << ',' << "Command" <<
17655  ',' << "OtherHeadCode" << ',' << "LinkedTrainEntryPtr" << ',' << "RearStartOrRepeatMins" << ',' << "FrontStartOrRepeatDigits" << ',' <<
17656  "RepeatNumber" << '\n' << '\n';
17657  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
17658  {
17659  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
17660  AnsiString TimetableEntryTypeStr;
17661  // NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere, FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat
17662  switch(AVEntry.FormatType)
17663  {
17664  case 0:
17665  {
17666  TimetableEntryTypeStr = "NoFormat";
17667  break;
17668  }
17669 
17670  case 1:
17671  {
17672  TimetableEntryTypeStr = "TimeLoc";
17673  break;
17674  }
17675 
17676  case 2:
17677  {
17678  TimetableEntryTypeStr = "TimeTimeLoc";
17679  break;
17680  }
17681 
17682  case 3:
17683  {
17684  TimetableEntryTypeStr = "TimeCmd";
17685  break;
17686  }
17687 
17688  case 4:
17689  {
17690  TimetableEntryTypeStr = "StartNew";
17691  break;
17692  }
17693 
17694  case 5:
17695  {
17696  TimetableEntryTypeStr = "TimeCmdHeadCode";
17697  break;
17698  }
17699 
17700  case 6:
17701  {
17702  TimetableEntryTypeStr = "FinRemHere";
17703  break;
17704  }
17705 
17706  case 7:
17707  {
17708  TimetableEntryTypeStr = "FNSShuttle";
17709  break;
17710  }
17711 
17712  case 8:
17713  {
17714  TimetableEntryTypeStr = "SNTShuttle";
17715  break;
17716  }
17717 
17718  case 9:
17719  {
17720  TimetableEntryTypeStr = "SNSShuttle";
17721  break;
17722  }
17723 
17724  case 10:
17725  {
17726  TimetableEntryTypeStr = "SNSNonRepeatFromShuttle";
17727  break;
17728  }
17729 
17730  case 11:
17731  {
17732  TimetableEntryTypeStr = "FSHNewService";
17733  break;
17734  }
17735 
17736  case 12:
17737  {
17738  TimetableEntryTypeStr = "Repeat";
17739  break;
17740  }
17741 
17742  default:
17743  {
17744  TimetableEntryTypeStr = "Default";
17745  break;
17746  }
17747  }
17748  OutFile << ',' << TimetableEntryTypeStr.c_str() << ',' << Utilities->Format96HHMM(AVEntry.EventTime).c_str() << ',' << Utilities->Format96HHMM
17749  (AVEntry.ArrivalTime).c_str() << ',' << Utilities->Format96HHMM(AVEntry.DepartureTime).c_str() << ',' << AVEntry.LocationName.c_str()
17750  << ',' << AVEntry.Command.c_str() << ',' << AVEntry.OtherHeadCode.c_str()
17751  << ',' << AVEntry.LinkedTrainEntryPtr << ',' << AVEntry.RearStartOrRepeatMins << ',' << AVEntry.FrontStartOrRepeatDigits << ',' <<
17752  AVEntry.NumberOfRepeats << '\n';
17753  }
17754  OutFile << '\n';
17755  OutFile << ',' << ',' << "Mass" << ',' << "MaxBrakeRate" << ',' << "PowerAtRail" << ',' << "TrainID" << ',' << "RunningEntry" << '\n' << '\n';
17756  for(unsigned int y = 0; y < TrainDataVector.at(x).TrainOperatingDataVector.size(); y++)
17757  {
17758  TTrainOperatingData TOD = TrainDataVector.at(x).TrainOperatingDataVector.at(y);
17759  AnsiString RunningEntryStr;
17760  // NotStarted, Running, Exited
17761  switch(TOD.RunningEntry)
17762  {
17763  case 0:
17764  {
17765  RunningEntryStr = "NotStarted";
17766  break;
17767  }
17768 
17769  case 1:
17770  {
17771  RunningEntryStr = "Running";
17772  break;
17773  }
17774 
17775  case 2:
17776  {
17777  RunningEntryStr = "Exited";
17778  break;
17779  }
17780  }
17781  OutFile << ',' << ',' << TOD.TrainID << ',' << RunningEntryStr.c_str() << ',' << '\n';
17782  }
17783  OutFile << '\n';
17784  }
17785  OutFile.close();
17786  Utilities->CallLogPop(1373);
17787 }
17788 
17789 // ---------------------------------------------------------------------------
17790 
17791 void TTrainController::StopTTClockMessage(int Caller, AnsiString Message)
17792 // ShowMessage stops everything so this function used where a message is needed when may be in Operating mode.
17793 // The timetable Restart and BaseTimes are reset so the timetable clock stops & restarts when 'OK' button pressed (in ClockTimer2 when StopTTClockFlag is false)
17794 {
17795  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StopTTClockMessage," + Message);
17796  StopTTClockFlag = true; // so TTClock stopped during MasterClockTimer function
17798  ShowMessage(Message);
17799  BaseTime = TDateTime::CurrentDateTime();
17800  StopTTClockFlag = false;
17801  Utilities->CallLogPop(1374);
17802 }
17803 
17804 // ---------------------------------------------------------------------------
17805 
17806 void TTrainController::SaveSessionTrains(int Caller, std::ofstream &SessionFile)
17807 // save *TrainDataEntryPtr & *ActionVectorEntryPtr as integer offsets
17808 // from the start of the relevant vectors. Can't save the pointer values
17809 // as these will be different each time the vectors are created
17810 {
17811  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionTrains");
17812  Utilities->SaveFileInt(SessionFile, TrainVector.size());
17813  for(unsigned int x = 0; x < TrainVector.size(); x++)
17814  {
17815  TrainVectorAt(55, x).SaveOneSessionTrain(0, SessionFile);
17816  }
17817  Utilities->CallLogPop(1375);
17818 }
17819 
17820 // ---------------------------------------------------------------------------
17821 
17822 void TTrainController::LoadSessionTrains(int Caller, std::ifstream &SessionFile)
17823 {
17824  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionTrains");
17825  int NumberOfTrains = Utilities->LoadFileInt(SessionFile);
17826  TTrain *NewTrain = new TTrain(1, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17827  // by zero error in calculating AValue, use 1
17828  for(int x = 0; x < NumberOfTrains; x++)
17829  {
17830  *NewTrain = TTrain(2, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17831  // by zero error in calculating AValue, use 1
17832  NewTrain->LoadOneSessionTrain(0, SessionFile);
17833  if((NewTrain->EntrySpeed < 1) && (NewTrain->PowerAtRail < 1))
17834  // added at v2.4.0. have to include as that value not stored in session file
17835  {
17836  NewTrain->StoppedWithoutPower = true;
17837  }
17838  TrainVector.push_back(*NewTrain);
17839  LastTrainLoaded = x;
17840  }
17841  delete NewTrain;
17842  Utilities->CallLogPop(1376);
17843 }
17844 
17845 // ---------------------------------------------------------------------------
17846 
17847 bool TTrainController::CheckSessionTrains(int Caller, std::ifstream &InFile)
17848 {
17849  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionTrains");
17850  int NumberOfTrains;
17851 
17852  if(!Utilities->CheckAndReadFileInt(InFile, 0, 10000, NumberOfTrains))
17853  {
17854  Utilities->CallLogPop(1377);
17855  return(false);
17856  }
17857  for(int x = 0; x < NumberOfTrains; x++)
17858  {
17859  if(!(TTrain::CheckOneSessionTrain(InFile)))
17860  {
17861  Utilities->CallLogPop(1378);
17862  return(false);
17863  }
17864  }
17865  Utilities->CallLogPop(1379);
17866  return(true);
17867 }
17868 
17869 // ---------------------------------------------------------------------------
17870 
17871 void TTrainController::SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
17872 {
17873  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionLockedRoutes");
17874  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.size());
17875  for(unsigned int x = 0; x < AllRoutes->LockedRouteVector.size(); x++)
17876  {
17877  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RouteNumber);
17878  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RearTrackVectorPosition);
17879  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastTrackVectorPosition);
17880  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastXLinkPos);
17881  Utilities->SaveFileDouble(SessionFile, double(AllRoutes->LockedRouteVector.at(x).LockStartTime));
17882  }
17883  Utilities->CallLogPop(1380);
17884 }
17885 
17886 // ---------------------------------------------------------------------------
17887 
17888 void TTrainController::LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17889 {
17890  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionLockedRoutes");
17891  TAllRoutes::TLockedRouteClass LockedRouteObject;
17892  int LockedRouteVectorSize = Utilities->LoadFileInt(SessionFile);
17893 
17894  for(int x = 0; x < LockedRouteVectorSize; x++)
17895  {
17896  LockedRouteObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17897  LockedRouteObject.RearTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17898  LockedRouteObject.LastTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17899  LockedRouteObject.LastXLinkPos = Utilities->LoadFileInt(SessionFile);
17900  double LockStartTimeDouble = Utilities->LoadFileDouble(SessionFile);
17901  LockedRouteObject.LockStartTime = TDateTime(LockStartTimeDouble);
17902  AllRoutes->LockedRouteVector.push_back(LockedRouteObject);
17903  }
17904  Utilities->CallLogPop(1381);
17905 }
17906 
17907 // ---------------------------------------------------------------------------
17908 
17909 bool TTrainController::CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17910 {
17911  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionLockedRoutes");
17912  int LockedRouteVectorSize;
17913 
17914  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, LockedRouteVectorSize))
17915  {
17916  Utilities->CallLogPop(1382);
17917  return(false);
17918  }
17919  for(int x = 0; x < LockedRouteVectorSize; x++)
17920  {
17921  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17922  {
17923  Utilities->CallLogPop(1383);
17924  return(false);
17925  }
17926  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17927  {
17928  Utilities->CallLogPop(1384);
17929  return(false);
17930  }
17931  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17932  {
17933  Utilities->CallLogPop(1385);
17934  return(false);
17935  }
17936  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17937  {
17938  Utilities->CallLogPop(1386);
17939  return(false);
17940  }
17941  if(!Utilities->CheckFileDouble(SessionFile))
17942  {
17943  Utilities->CallLogPop(1387);
17944  return(false);
17945  }
17946  }
17947  Utilities->CallLogPop(1388);
17948  return(true);
17949 }
17950 
17951 // ---------------------------------------------------------------------------
17952 
17953 void TTrainController::SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
17954 {
17955  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionContinuationAutoSigEntries");
17956  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.size());
17957  for(unsigned int x = 0; x < ContinuationAutoSigVector.size(); x++)
17958  {
17959  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).RouteNumber);
17960  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).AccessNumber);
17961  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).FirstDelay);
17962  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).SecondDelay);
17963  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).ThirdDelay);
17964  Utilities->SaveFileDouble(SessionFile, double(ContinuationAutoSigVector.at(x).PassoutTime));
17965  }
17966  Utilities->CallLogPop(1389);
17967 }
17968 
17969 // ---------------------------------------------------------------------------
17970 
17971 void TTrainController::LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17972 {
17973  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionContinuationAutoSigEntries");
17974  TContinuationAutoSigEntry ContinuationAutoSigObject;
17975  int ContinuationAutoSigVectorSize = Utilities->LoadFileInt(SessionFile);
17976 
17977  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17978  {
17979  ContinuationAutoSigObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17980  ContinuationAutoSigObject.AccessNumber = Utilities->LoadFileInt(SessionFile);
17981  ContinuationAutoSigObject.FirstDelay = Utilities->LoadFileDouble(SessionFile);
17982  ContinuationAutoSigObject.SecondDelay = Utilities->LoadFileDouble(SessionFile);
17983  ContinuationAutoSigObject.ThirdDelay = Utilities->LoadFileDouble(SessionFile);
17984  double PassoutTimeDouble = Utilities->LoadFileDouble(SessionFile);
17985  ContinuationAutoSigObject.PassoutTime = TDateTime(PassoutTimeDouble);
17986  ContinuationAutoSigVector.push_back(ContinuationAutoSigObject);
17987  }
17988  Utilities->CallLogPop(1390);
17989 }
17990 
17991 // ---------------------------------------------------------------------------
17992 
17993 bool TTrainController::CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17994 {
17995  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionContinuationAutoSigEntries");
17996  int ContinuationAutoSigVectorSize;
17997 
17998  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, ContinuationAutoSigVectorSize))
17999  {
18000  Utilities->CallLogPop(1391);
18001  return(false);
18002  }
18003  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
18004  {
18005  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
18006  {
18007  Utilities->CallLogPop(1392);
18008  return(false);
18009  }
18010  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
18011  {
18012  Utilities->CallLogPop(1393);
18013  return(false);
18014  }
18015  if(!Utilities->CheckFileDouble(SessionFile))
18016  {
18017  Utilities->CallLogPop(1405);
18018  return(false);
18019  }
18020  if(!Utilities->CheckFileDouble(SessionFile))
18021  {
18022  Utilities->CallLogPop(1406);
18023  return(false);
18024  }
18025  if(!Utilities->CheckFileDouble(SessionFile))
18026  {
18027  Utilities->CallLogPop(1407);
18028  return(false);
18029  }
18030  if(!Utilities->CheckFileDouble(SessionFile))
18031  {
18032  Utilities->CallLogPop(1394);
18033  return(false);
18034  }
18035  }
18036  Utilities->CallLogPop(1395);
18037  return(true);
18038 }
18039 
18040 // ---------------------------------------------------------------------------
18041 
18042 /*
18043  class TContinuationTrainExpectationEntry //for expected trains at continuation entries
18044  {
18045  public:
18046  AnsiString Description; ///< service description
18047  AnsiString HeadCode; ///< service headcode
18048  int RepeatNumber; ///< service RepeatNumber
18049  int IncrementalMinutes; ///< Repeat separation in minutes
18050  int IncrementalDigits; ///< Repeat headcode separation
18051  int VectorPosition; ///< TrackVectorPosition for the continuation element
18052  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
18053  };
18054 
18055 
18056  typedef std::multimap<TDateTime,TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
18057  typedef pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair;
18058 */
18059 
18061 // build this into timetable load so session loading can use it too
18062 // being a multimap it automatically sorts in ascending EventTime order
18063 {
18064  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BuildContinuationTrainExpectationMultiMap");
18066  // need to clear as this called twice when load a session
18067  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
18068  {
18069  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
18070  const TActionVectorEntry &AVFirstEntry = TDEntry.ActionVector.at(0);
18071  const TActionVectorEntry &AVLastEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
18072 
18073  if(AVFirstEntry.Command == "Snt")
18074  // new train (no need to include Snt-sh since they can't start at a continuation)
18075  {
18078  {
18080  CTEEntry.VectorPosition = AVFirstEntry.RearStartOrRepeatMins;
18081  // retains this value for all repeats
18082  CTEEntry.RepeatNumber = 0; // for first entry
18083  CTEEntry.TrainDataEntryPtr = &TDEntry;
18084  // retains this value for all repeats
18085  CTEEntry.HeadCode = TDEntry.HeadCode;
18086  CTEEntry.FixedDescription = TDEntry.FixedDescription; //name changed at v2.16.1
18087  CTEEntry.IncrementalMinutes = 0;
18088  CTEEntry.IncrementalDigits = 0;
18089  if(AVLastEntry.FormatType == Repeat)
18090  {
18091  CTEEntry.IncrementalMinutes = AVLastEntry.RearStartOrRepeatMins;
18092  // retains this value or 0 for all repeats
18093  CTEEntry.IncrementalDigits = AVLastEntry.FrontStartOrRepeatDigits;
18094  // retains this value or 0 for all repeats
18095  }
18096  CTEMMP.first = AVFirstEntry.EventTime;
18097  CTEMMP.second = CTEEntry;
18098  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
18099  // base entry
18100  if(TDEntry.NumberOfTrains > 1)
18101  {
18102  if(AVLastEntry.FormatType != Repeat)
18103  {
18104  throw Exception("Error, Last ActionVectorEntry not a repeat in BuildContinuationTrainExpectationMultiMap");
18105  }
18106  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
18107  {
18108  CTEEntry.RepeatNumber = y;
18109  CTEEntry.HeadCode = GetRepeatHeadCode(23, TDEntry.HeadCode, y, AVLastEntry.FrontStartOrRepeatDigits);
18110  // CTEEntry.VectorPosition stays same
18111  CTEMMP.first = GetRepeatTime(3, AVFirstEntry.EventTime, y, AVLastEntry.RearStartOrRepeatMins);
18112  CTEMMP.second = CTEEntry;
18113  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
18114  }
18115  }
18116  }
18117  }
18118  }
18119  Utilities->CallLogPop(1396);
18120 }
18121 
18122 // ---------------------------------------------------------------------------
18123 
18125 {
18126  // called when WarningFlashCount == 0 or when press zoomout button
18127  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainsInZoomOutMode");
18128  if(!Display->ZoomOutFlag)
18129  {
18130  Utilities->CallLogPop(1156);
18131  return;
18132  }
18133  for(unsigned int x = 0; x < TrainVector.size(); x++)
18134  {
18135  // plot blanks & track for all train, even if to be overplotted, since when flashing need to overplot all anyway
18136  // if OldPlotElement[x] == -1 then ignore (not plotted)
18138  TrainVectorAt(57, x).PlotTrainInZoomOutMode(0, Flash);
18139  }
18140  Display->Update();
18141  // need to keep this since Update() not called for PlotSmallOutput as too slow
18142  Utilities->CallLogPop(742);
18143 }
18144 
18145 // ---------------------------------------------------------------------------
18146 
18147 TTrain &TTrainController::TrainVectorAt(int Caller, int VecPos)
18148 {
18149  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAt," + AnsiString(VecPos));
18150  if((VecPos < 0) || (VecPos >= (int)TrainVector.size()))
18151  {
18152  throw Exception("Out of Range Error, vector size: " + AnsiString(TrainVector.size()) + ", VecPos: " + AnsiString(VecPos) + " in TrainVectorAt");
18153  }
18154  Utilities->CallLogPop(740);
18155  return(TrainVector.at(VecPos));
18156 }
18157 
18158 // ---------------------------------------------------------------------------
18159 
18160 void TTrainController::CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
18161 {
18162  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateFormattedTimetable");
18163  AnsiString RetStr = "", PartStr = "";
18164 
18165 
18166 /*
18167  Have description & mass etc for train at top - header, then array of actions
18168 
18169  class TActionVectorEntry
18170  {
18171  public:
18172  AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode;
18174  bool SignallerControl;
18176  bool Warning;
18178  int NumberOfRepeats;
18180  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
18182  TDateTime EventTime, ArrivalTime, DepartureTime;
18184  TNumList ExitList;
18186  TTimetableFormatType FormatType;
18188  TTimetableLocationType LocationType;
18190  TTimetableSequenceType SequenceType;
18192  TTimetableShuttleLinkType ShuttleLinkType;
18194  TTrainDataEntry *LinkedTrainEntryPtr;
18196  TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr;
18198 
18199  typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
18200 
18201  enum TRunningEntry {NotStarted, Running, Exited};//contains status info for each train
18202 
18203  class TTrainOperatingData
18204  {
18205  public:
18206  int TrainID;
18207  TActionEventType EventReported;
18208  TRunningEntry RunningEntry;
18209 
18210  //inline function
18211  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;}//ID -1 = marker for not running
18212  };
18213 
18214  typedef std::vector<TTrainOperatingData> TTrainOperatingDataVector;
18215 
18216  class TTrainDataEntry
18217  {
18218  public:
18219  AnsiString HeadCode, ServiceReference, Description;
18221  double MaxBrakeRate;
18223  double MaxRunningSpeed;
18225  double PowerAtRail;
18227  int Mass;
18229  int NumberOfTrains;
18231  int SignallerSpeed;
18233  int StartSpeed;
18235  TActionVector ActionVector;
18237  TTrainOperatingDataVector TrainOperatingDataVector;
18239 
18240  //inline function
18241  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;}
18242  };
18243 
18244  typedef std::vector<TTrainDataEntry> TTrainDataVector;//object is a member of TTrainController & contains the whole timetable
18245 
18246  //formatted timetable types
18247  class TOneTrainFormattedEntry
18248  {
18249  AnsiString Action;//includes location if relevanr
18250  AnsiString Time;
18251  };
18252 
18253  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
18254 
18255  class TOneCompleteFormattedTrain//headcode + list of actions
18256  {
18257  public:
18258  AnsiString HeadCode;
18259  TOneFormattedTrainVector OneFormattedTrainVector;
18260  };
18261 
18262  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
18263 
18264  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
18265  {
18266  public:
18267  AnsiString Header;//description, mass, power, brake rate etc
18268  int NumberOfTrains;// number of repeats + 1
18269  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
18270  };
18271 
18272 
18273  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
18274  //end of formatted timetable types
18275 
18276 */
18277 
18278  AnsiString TTFileName = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18279 
18280  // format "16/06/2009 20:55:17"
18281  // avoid characters in filename:= / \ : * ? " < > |
18282  TTFileName = CurDir + "\\Formatted timetables\\Timetable " + TTFileName + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
18283 
18284  AnsiString ShortTTName = "";
18285 
18286  for(int x = TTFileName.Length(); x > 0; x--)
18287  {
18288  if(TTFileName[x] == '\\')
18289  {
18290  ShortTTName = TTFileName.SubString(x + 1, TTFileName.Length() - x - 4);
18291  break;
18292  }
18293  }
18294 
18295  ShowMessage("Creates two timetables named " + ShortTTName +
18296  " in the 'Formatted timetables' folder, one in service order in '.csv' format, and one in chronological order in '.txt' format");
18297 
18298  Screen->Cursor = TCursor(-11); // Hourglass
18299 
18300  AnsiString FormatNoDPStr = "#######0";
18301  AnsiString TableTitle = "", TimetableTimeStr = "", MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "", FirstHeadCode = "", Header = "";
18302 
18304  TableTitle = "Railway: " + RailwayTitle + "; Timetable: " + TimetableTitle + "; Start time: " + TimetableTimeStr;
18305  TAllFormattedTrains *AllTTTrains = new TAllFormattedTrains;
18306 
18307  // all timetable in formatted form
18308  //create the AllTTTrains vector
18309  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
18310  {
18311  MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "";
18312  const TTrainDataEntry &TrainDataEntry = TrainDataVector.at(x);
18313  if(TrainDataEntry.Mass > 0)
18314  {
18315  MassStr = "; Mass " + AnsiString::FormatFloat(FormatNoDPStr, ((double)TrainDataEntry.Mass) / 1000) + "Te; ";
18316  }
18317  if(TrainDataEntry.PowerAtRail > 0)
18318  {
18319  PowerStr = "Power " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.PowerAtRail / 1000 / 0.8) + "kW; ";
18320  }
18321  if(TrainDataEntry.MaxBrakeRate > 0)
18322  {
18323  BrakeStr = "Brake force " + AnsiString::FormatFloat(FormatNoDPStr, (TrainDataEntry.MaxBrakeRate * TrainDataEntry.Mass / 9810)) + "Te; ";
18324  }
18325  if(TrainDataEntry.MaxRunningSpeed > 0)
18326  {
18327  MaxSpeedStr = "Maximum speed " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.MaxRunningSpeed) + " km/h";
18328  }
18329  FirstHeadCode = TrainDataEntry.HeadCode;
18330  int IncDigits = 0, IncMinutes = 0;
18331  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18332  if(!ActionVector.empty())
18333  {
18334  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
18335  {
18336  IncDigits = ActionVector.at(ActionVector.size() - 1).FrontStartOrRepeatDigits;
18337  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18338  }
18339  }
18340  TTrainFormattedInformation OneTTLine;
18341  // contains all information for a single TT entry (including repeats)
18342  for(int y = 0; y < TrainDataEntry.NumberOfTrains; y++)
18343  {
18344  OneTTLine.Header = "";
18345  if((TrainDataEntry.FixedDescription != "") && (MassStr != "")) //name changed at v2.16.1
18346  {
18347  OneTTLine.Header = TrainDataEntry.FixedDescription + MassStr + PowerStr + BrakeStr + MaxSpeedStr; //name changed at v2.16.1
18348  }
18349  else if(TrainDataEntry.FixedDescription != "") //name changed at v2.16.1
18350  {
18351  OneTTLine.Header = TrainDataEntry.FixedDescription; //name changed at v2.16.1
18352  }
18353  OneTTLine.NumberOfTrains = TrainDataEntry.NumberOfTrains;
18354  TOneCompleteFormattedTrain OneTTTrain; // headcode + list of actions
18355  for(unsigned int z = 0; z < ActionVector.size(); z++)
18356  {
18357  TOneTrainFormattedEntry OneTTEntry;
18358  OneTTTrain.HeadCode = GetRepeatHeadCode(24, FirstHeadCode, y, IncDigits);
18359  TActionVectorEntry ActionVectorEntry = ActionVector.at(z);
18360  AnsiString PartStr = "", TimeStr = "";
18361 /*
18362  enum TTimetableFormatType {NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere,
18363  FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat, PassTime,
18364  ExitRailway};
18365  enum TTimetableSequenceType {NoSequence, StartSequence, FinishSequence, IntermediateSequence, SequTypeForRepeatEntry};
18366  enum TTimetableLocationType {NoLocation, AtLocation, EnRoute, LocTypeForRepeatEntry};
18367  enum TTimetableShuttleLinkType {NoShuttleLink, NotAShuttleLink, ShuttleLink, ShuttleLinkTypeForRepeatEntry};
18368 */
18369  if(ActionVectorEntry.SequenceType == StartSequence)
18370  {
18371  if(ActionVectorEntry.FormatType == StartNew)
18372  {
18373  if(ActionVectorEntry.LocationName != "")
18374  {
18375  if(Track->TrackElementAt(742, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
18376  {
18377  PartStr = "Enters at " + ActionVectorEntry.LocationName;
18378  }
18379  else
18380  {
18381  PartStr = "Created at " + ActionVectorEntry.LocationName;
18382  }
18383  }
18384  else // may be a named continuation or other element, and if so report that
18385  {
18386  AnsiString LocName = Track->TrackElementAt(739, ActionVectorEntry.RearStartOrRepeatMins).ActiveTrackElementName;
18387  if(Track->TrackElementAt(740, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
18388  {
18389  if(LocName != "")
18390  {
18391  PartStr = "Enters at " + LocName;
18392  }
18393  else // use rear position if it's a continuation
18394  {
18395  PartStr = "Enters at " + Track->TrackElementAt(737, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
18396  }
18397  }
18398  else // not a continuation
18399  {
18400  if(LocName != "")
18401  // if not a continuation then LocName should be same as ActionVectorEntry.LocationName
18402  // but include anyway
18403  {
18404  PartStr = "Created at " + LocName;
18405  }
18406  else // use rear position again
18407  {
18408  PartStr = "Created at " + Track->TrackElementAt(741, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
18409  }
18410  }
18411  }
18412  TimeStr = Utilities->Format96HHMM(GetRepeatTime(20, ActionVectorEntry.EventTime, y, IncMinutes));
18413  }
18414  else if(ActionVectorEntry.FormatType == SNTShuttle)
18415  {
18416  if(y == 0) // first train
18417  {
18418  PartStr = "Enters at " + ActionVectorEntry.LocationName;
18419  TimeStr = Utilities->Format96HHMM(GetRepeatTime(21, ActionVectorEntry.EventTime, y, IncMinutes));
18420  }
18421  else
18422  {
18423  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18424  TimeStr = GetRepeatHeadCode(45, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18425  Utilities->Format96HHMM(GetRepeatTime(26, ActionVectorEntry.EventTime, y, IncMinutes));
18426  } // y-1 for headcode above since it is the last repeat value that the train is from
18427 
18428  }
18429  else if(ActionVectorEntry.Command == "Sfs")
18430  {
18431  PartStr = "New service at " + ActionVectorEntry.LocationName + " split from";
18432  TimeStr = GetRepeatHeadCode(33, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18433  Utilities->Format96HHMM(GetRepeatTime(24, ActionVectorEntry.EventTime, y, IncMinutes));
18434  }
18435  else if(ActionVectorEntry.Command == "Sns")
18436  {
18437  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18438  TimeStr = GetRepeatHeadCode(34, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18439  Utilities->Format96HHMM(GetRepeatTime(25, ActionVectorEntry.EventTime, y, IncMinutes));
18440  }
18441  else if(ActionVectorEntry.FormatType == SNSShuttle)
18442  {
18443  if(y == 0) // first entry from shuttle
18444  {
18445  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18446  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " +
18447  Utilities->Format96HHMM(GetRepeatTime(27, ActionVectorEntry.EventTime, y, IncMinutes));
18448  }
18449  else
18450  {
18451  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18452  TimeStr = GetRepeatHeadCode(35, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18453  Utilities->Format96HHMM(GetRepeatTime(22, ActionVectorEntry.EventTime, y, IncMinutes));
18454  } // y-1 for headcode above since it is the last repeat value that the train is from
18455 
18456  }
18457  else if(ActionVectorEntry.FormatType == SNSNonRepeatFromShuttle)
18458  {
18459  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18460  // need repeat for the non-repeating headcode as it's the last train of the repeating shuttle
18461  TTrainDataEntry *TDE = ActionVectorEntry.LinkedTrainEntryPtr;
18462  AnsiString FirstHeadCode = TDE->HeadCode;
18463  int LastRepeatNumber = TDE->NumberOfTrains - 1;
18464  // a shuttle has to have at least 1 repeat
18465  int IncrementalDigits = TDE->ActionVector.at(TDE->ActionVector.size() - 1).FrontStartOrRepeatDigits;
18466  TimeStr = GetRepeatHeadCode(36, FirstHeadCode, LastRepeatNumber, IncrementalDigits) + " at " +
18467  Utilities->Format96HHMM(GetRepeatTime(23, ActionVectorEntry.EventTime, y, IncMinutes));
18468  }
18469  }
18470  else if(ActionVectorEntry.SequenceType == IntermediateSequence)
18471  {
18472  if(ActionVectorEntry.FormatType == TimeTimeLoc)
18473  {
18474  // here need 2 entries if times different so push the first right away & the second later
18475  // if times same just give the arrival entry
18476  if(ActionVectorEntry.DepartureTime != ActionVectorEntry.ArrivalTime)
18477  {
18478  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18479  TimeStr = Utilities->Format96HHMM(GetRepeatTime(4, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18480  OneTTEntry.Action = PartStr;
18481  OneTTEntry.Time = TimeStr;
18482  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18483  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18484  TimeStr = Utilities->Format96HHMM(GetRepeatTime(5, ActionVectorEntry.DepartureTime, y, IncMinutes));
18485  }
18486  else
18487  {
18488  PartStr = "Arrives & departs " + ActionVectorEntry.LocationName;
18489  TimeStr = Utilities->Format96HHMM(GetRepeatTime(29, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18490  }
18491  }
18492  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime != TDateTime(-1)))
18493  {
18494  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18495  TimeStr = Utilities->Format96HHMM(GetRepeatTime(6, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18496  }
18497  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime == TDateTime(-1)))
18498  {
18499  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18500  TimeStr = Utilities->Format96HHMM(GetRepeatTime(7, ActionVectorEntry.DepartureTime, y, IncMinutes));
18501  }
18502  else if(ActionVectorEntry.FormatType == PassTime)
18503  {
18504  PartStr = "Passes " + ActionVectorEntry.LocationName;
18505  TimeStr = Utilities->Format96HHMM(GetRepeatTime(8, ActionVectorEntry.EventTime, y, IncMinutes));
18506  }
18507  else if(ActionVectorEntry.Command == "jbo")
18508  {
18509  PartStr = "Joined at " + ActionVectorEntry.LocationName + " by";
18510  TimeStr = GetRepeatHeadCode(37, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18511  Utilities->Format96HHMM(GetRepeatTime(9, ActionVectorEntry.EventTime, y, IncMinutes));
18512  }
18513  else if(ActionVectorEntry.Command == "fsp")
18514  {
18515  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18516  {
18517  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18518  }
18519  else
18520  {
18521  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18522  }
18523  TimeStr = GetRepeatHeadCode(38, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18524  Utilities->Format96HHMM(GetRepeatTime(10, ActionVectorEntry.EventTime, y, IncMinutes));
18525  }
18526  else if(ActionVectorEntry.Command == "rsp")
18527  {
18528  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18529  {
18530  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18531  }
18532  else
18533  {
18534  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18535  }
18536  TimeStr = GetRepeatHeadCode(39, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18537  Utilities->Format96HHMM(GetRepeatTime(11, ActionVectorEntry.EventTime, y, IncMinutes));
18538  }
18539  else if(ActionVectorEntry.Command == "cdt")
18540  {
18541  PartStr = "Changes direction at " + ActionVectorEntry.LocationName;
18542  TimeStr = Utilities->Format96HHMM(GetRepeatTime(12, ActionVectorEntry.EventTime, y, IncMinutes));
18543  }
18544  else if(ActionVectorEntry.Command == "dsc")
18545  {
18546  PartStr = "Changes description at " + ActionVectorEntry.LocationName;
18547  TimeStr = Utilities->Format96HHMM(GetRepeatTime(76, ActionVectorEntry.EventTime, y, IncMinutes));
18548  }
18549  else if(ActionVectorEntry.Command == "cms")
18550  {
18551  PartStr = "Change maximum speed to " + ActionVectorEntry.NewMaxSpeed + " at " + ActionVectorEntry.LocationName;
18552  TimeStr = Utilities->Format96HHMM(GetRepeatTime(78, ActionVectorEntry.EventTime, y, IncMinutes));
18553  }
18554  }
18555  else if(ActionVectorEntry.SequenceType == FinishSequence)
18556  {
18557  if(ActionVectorEntry.Command == "Fns")
18558  {
18559  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18560  TimeStr = GetRepeatHeadCode(40, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18561  Utilities->Format96HHMM(GetRepeatTime(13, ActionVectorEntry.EventTime, y, IncMinutes));
18562  }
18563  else if(ActionVectorEntry.Command == "F-nshs")
18564  {
18565  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18566  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18567  (GetRepeatTime(17, ActionVectorEntry.EventTime, y, IncMinutes));
18568  }
18569  else if((ActionVectorEntry.Command == "Fns-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18570  {
18571  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service ";
18572  TimeStr = GetRepeatHeadCode(41, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18573  Utilities->Format96HHMM(GetRepeatTime(14, ActionVectorEntry.EventTime, y, IncMinutes));
18574  // y+1 because it's the NEXT service repeat number that is relevant
18575  }
18576  else if((ActionVectorEntry.Command == "Fns-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18577  {
18578  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18579  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18580  (GetRepeatTime(15, ActionVectorEntry.EventTime, y, IncMinutes));
18581  }
18582  else if((ActionVectorEntry.Command == "Frh-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18583  {
18584  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18585  TimeStr = GetRepeatHeadCode(43, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18586  Utilities->Format96HHMM(GetRepeatTime(16, ActionVectorEntry.EventTime, y, IncMinutes));
18587  // y+1 because it's the NEXT service repeat number that is relevant
18588  }
18589  else if((ActionVectorEntry.Command == "Frh-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18590  {
18591  PartStr = "Terminates shuttle service at " + ActionVectorEntry.LocationName;
18592  // only used in chronological tt
18593  TimeStr = "End at " + Utilities->Format96HHMM(GetRepeatTime(28, ActionVectorEntry.EventTime, y, IncMinutes));
18594  // the "End at " is stripped out of the chronological tt but displayed in the traditional tt
18595  }
18596  else if(ActionVectorEntry.Command == "Frh")
18597  {
18598  PartStr = "Terminates at " + ActionVectorEntry.LocationName;
18599  // need here to examine the time of the preceding entry, may be ArrivalTime if TimeLoc, or EventTime otherwise
18600  if(z > 0)
18601  // should be for finish entry but include check for safety
18602  {
18603  if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
18604  {
18605  TimeStr = Utilities->Format96HHMM(GetRepeatTime(30, ActionVector.at(z - 1).EventTime, y, IncMinutes));
18606  }
18607  else if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
18608  {
18609  TimeStr = Utilities->Format96HHMM(GetRepeatTime(31, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
18610  }
18611  else
18612  {
18613  TimeStr = " "; // shouldn't ever get here
18614  }
18615  }
18616  }
18617  else if(ActionVectorEntry.Command == "Fer")
18618  {
18619  AnsiString AllowedExits;
18620  PartStr = "Exits railway" + GetExitLocationAndAt(0, ActionVectorEntry.ExitList, AllowedExits) + AllowedExits;
18621  TimeStr = Utilities->Format96HHMM(GetRepeatTime(18, ActionVectorEntry.EventTime, y, IncMinutes));
18622  }
18623  else if(ActionVectorEntry.Command == "Fjo")
18624  {
18625  PartStr = "At " + ActionVectorEntry.LocationName + " joins";
18626  TimeStr = GetRepeatHeadCode(44, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18627  Utilities->Format96HHMM(GetRepeatTime(19, ActionVectorEntry.EventTime, y, IncMinutes));
18628  }
18629  }
18630  else if(ActionVectorEntry.SequenceType == SequTypeForRepeatEntry)
18631  {
18632  continue; // no entry needed for a repeat
18633  }
18634  OneTTEntry.Action = PartStr;
18635  OneTTEntry.Time = TimeStr;
18636  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18637  // one per action
18638  }
18639  OneTTLine.OneCompleteFormattedTrainVector.push_back(OneTTTrain);
18640  // one per repeat
18641  }
18642  AllTTTrains->push_back(OneTTLine); // one per repeating train
18643  }
18644  // AllTTTrains vector now complete
18645 
18646  std::ofstream TTFile(TTFileName.c_str()); //formatted timetable
18647 
18648  if(TTFile == 0)
18649  {
18650  StopTTClockMessage(64, "Formatted timetable file failed to open - can't be created");
18651  delete AllTTTrains;
18652  Utilities->CallLogPop(1567);
18653  return;
18654  }
18655 /* formatted timetable types
18656  class TOneTrainFormattedEntry
18657  {
18658  AnsiString Action;//includes location if relevant
18659  AnsiString Time;
18660  };
18661 
18662  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
18663 
18664  class TOneCompleteFormattedTrain//headcode + list of actions
18665  {
18666  public:
18667  AnsiString HeadCode;
18668  TOneFormattedTrainVector OneFormattedTrainVector;
18669  };
18670 
18671  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
18672 
18673  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
18674  {
18675  public:
18676  AnsiString Header;//description, mass, power, brake rate etc
18677  int NumberOfTrains;// number of repeats + 1
18678  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
18679  };
18680 
18681  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
18682  //end of formatted timetable types
18683 */
18684 
18685  // new layout using multiple rows
18686  TTFile << TableTitle.c_str() << '\n' << '\n';
18687  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18688  {
18689  TTFile << AllTTTrains->at(x).Header.c_str();
18690  TTFile << '\n';
18691  TTFile << ','; // for the blank line above the action list
18692  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18693  {
18694  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18695  {
18696  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str() << ',';
18697  }
18698  else
18699  {
18700  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str();
18701  }
18702  }
18703  TTFile << '\n' << '\n';
18704 
18705  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.size(); z++)
18706  {
18707  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.at(z).Action.c_str() << ',';
18708  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18709  {
18710  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18711  {
18712  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str() << ',';
18713  }
18714  else
18715  {
18716  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str();
18717  }
18718  }
18719  TTFile << '\n';
18720  }
18721  TTFile << '\n' << '\n';
18722  }
18723 
18724  TTFile.close();
18725 
18726  AnsiString TTFileName2 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18727 
18728  TTFileName2 = CurDir + "\\Formatted timetables\\Timetable " + TTFileName2 + "; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18729 
18730  std::ofstream TTFile2(TTFileName2.c_str()); //chronological timetable
18731 
18732  if(TTFile2 == 0)
18733  {
18734  StopTTClockMessage(67, "Chronological timetable file failed to open - can't be created");
18735  delete AllTTTrains;
18736  Utilities->CallLogPop(1710);
18737  return;
18738  }
18739  typedef std::multimap<AnsiString, AnsiString>TAnsiMultiMap;
18740  std::multimap<AnsiString, AnsiString>::iterator AMMIT;
18741  std::pair<AnsiString, AnsiString>AnsiMultiMapEntry;
18742 
18743  TAnsiMultiMap *TAMM = new TAnsiMultiMap;
18744  LastTTTime = ""; //records the very last time in the timetable - used in analysis file for Frh entries
18745 
18746  // multimap of AnsiStrings with TimeString as key (to sort automatically)
18747 
18748  TTFile2 << TableTitle.c_str() << '\n' << '\n';
18749  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18750  {
18751  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18752  {
18753  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.size(); z++)
18754  {
18755  bool GiveMessagesFalse = false;
18756  AnsiString TimeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time;
18757  AnsiString HeadCodeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode;
18758  AnsiString ActionString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Action;
18759  if(CheckHeadCodeValidity(11, GiveMessagesFalse, TimeString.SubString(1, 4)))
18760  // 'NXNN at HH:MM' (will return true if H/C as integ check passed)
18761  {
18762  // fails for HH:MM because of ':' or 'End at HH:MM' because of ' '
18763  AnsiString OtherHeadCode = TimeString.SubString(1, 4);
18764  TimeString = TimeString.SubString(9, 5);
18765  ActionString += " " + OtherHeadCode;
18766  }
18767  if(TimeString.SubString(1, 7) == "End at ")
18768  // for Frh-sh final entry
18769  {
18770  TimeString = TimeString.SubString(8, 5);
18771  }
18772  AnsiString OneLine = TimeString + ' ' + HeadCodeString + ' ' + ActionString + '\n';
18773  AnsiMultiMapEntry.first = TimeString;
18774  AnsiMultiMapEntry.second = OneLine;
18775  TAMM->insert(AnsiMultiMapEntry);
18776  }
18777  }
18778  }
18779 
18780  for(AMMIT = TAMM->begin(); AMMIT != TAMM->end(); AMMIT++)
18781  {
18782  TTFile2 << (AMMIT->second).c_str();
18783  }
18784  delete AllTTTrains;
18785  delete TAMM;
18786  TTFile2.close();
18787  Utilities->CallLogPop(1580);
18788 }
18789 
18790 // ---------------------------------------------------------------------------
18791 
18792 bool TTrainController::CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked,
18793  bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
18794 {
18795 
18796  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateTTAnalysisFile");
18797  bool AnalysisError = false;
18798  AnsiString SequenceLog = "SequenceLog\n";
18799 
18800 /* Double crosslink (shuttle) table:
18801 
18802 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
18803  Code ShuttleLink- EntryPtr ShuttleLink-
18804  HeadCode EntryPtr
18805 
18806 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
18807 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
18808 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18809 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
18810 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18811 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
18812 
18813 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
18814 */
18815 
18816  try
18817  {
18818  //New section at v2.5.0 for tt conflict analysis
18819  /*
18820  typedef std::list<AnsiString> TServiceCallingLocsList;
18821  typedef std::map<AnsiString, TServiceCallingLocsList> TAllServiceCallingLocsMap;
18822 
18824  struct TLocServiceTimes
18825  {
18826  AnsiString Location;
18827  AnsiString ServiceAndRepeatNum;
18828  AnsiString AtLocTime;
18829  AnsiString ArrTime;
18830  AnsiString DepTime;
18831  AnsiString FrhMarker;
18832  };
18833  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18834  */
18835 
18836  //first have to check through all the services and give each one a unique name, or the analysis won't recognise differences between services that have the same reference
18837  //to do that need a new TrainDataVector as don't want to change anything in the original. TrainDataVectorCopy is used for building AllServiceCallingLocsMap & LocServiceTimesVector
18838 
18839 //create TrainDataVectorCopy and populate service refs with /1, /2 etc
18840  TrainDataVectorCopy = TrainDataVector; //don't need it on heap as TrainController is on the heap. Didn't need others in CreatFormattedTimetables but leave as is.
18841  TTrainDataVector::iterator TDVIt, TDVCopyIt;
18842  int Suffix = 0;
18843  int IteratorNumber = 0;
18844  AnsiString AnsiSuffix = "";
18845  for(TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end() - 1; TDVIt++)
18846  {
18847  IteratorNumber++; //first value in loop is 1
18848  Suffix = 0;
18849  for(TDVCopyIt = TrainDataVectorCopy.begin() + IteratorNumber; TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18850  {
18851  if(TDVCopyIt->HeadCode == TDVIt->HeadCode)
18852  {
18853  Suffix++; //first value is 1
18854  AnsiSuffix = AnsiString(Suffix);
18855  TDVCopyIt->ServiceReference = TDVIt->HeadCode + "/" + AnsiSuffix; //set both the HeadCode + any forward slashes and numbers, this is because sometimes
18856  TDVCopyIt->HeadCode = TDVIt->HeadCode + "/" + AnsiSuffix; //service refs are used and sometimes H/Cs, so need them to be the same,
18857  } //they are all unique at this point anyway
18858  }
18859  }
18860 //now make all linked pointers in ActionVectorEntries point to links in the vector copy (still point to original vector at this stage)
18861 //and set the linked headcodes to the correct values - all unique at this point
18862  int Increment = 0, SlashPos;
18863  TActionVectorIterator AVEIt;
18864  AnsiString LinkedHeadCode;
18865 
18866  for(TDVCopyIt = TrainDataVectorCopy.begin(); TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18867  {
18868  for(AVEIt = TDVCopyIt->ActionVector.begin(); AVEIt != TDVCopyIt->ActionVector.end(); AVEIt++)
18869  {
18870  if(AVEIt->LinkedTrainEntryPtr != NULL)
18871  {
18872  Increment = AVEIt->LinkedTrainEntryPtr - &TrainDataVector.at(0);
18873  AVEIt->LinkedTrainEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18874  //now set AVEIt->OtherHeadCode to the linked headcodes with /1, /2 etc
18875  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18876  LinkedHeadCode = (*AVEIt->LinkedTrainEntryPtr).ServiceReference;
18877  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18878  SlashPos = 0;
18879  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18880  {
18881  if(LinkedHeadCode[x] == '/')
18882  {
18883  SlashPos = LinkedHeadCode.Length() - x + 1;
18884  break;
18885  }
18886  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18887  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18888  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18889  {
18890  break;
18891  }
18892  }
18893  //now strip off any prefix
18894  AVEIt->OtherHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18895  }
18896  else
18897  {
18898  AVEIt->OtherHeadCode = "";
18899  }
18900  if(AVEIt->NonRepeatingShuttleLinkEntryPtr != NULL)
18901  {
18902  Increment = AVEIt->NonRepeatingShuttleLinkEntryPtr - &TrainDataVector.at(0);
18903  AVEIt->NonRepeatingShuttleLinkEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18904  //now set AVEIt->NonRepeatingShuttleLinkHeadCode to the linked headcodes with /1, /2 etc
18905  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18906  LinkedHeadCode = (*AVEIt->NonRepeatingShuttleLinkEntryPtr).ServiceReference;
18907  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18908  SlashPos = 0;
18909  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18910  {
18911  if(LinkedHeadCode[x] == '/')
18912  {
18913  SlashPos = LinkedHeadCode.Length() - x + 1;
18914  break;
18915  }
18916  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18917  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18918  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18919  {
18920  break;
18921  }
18922  }
18923  //now strip off any prefix
18924  AVEIt->NonRepeatingShuttleLinkHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18925  }
18926  else
18927  {
18928  AVEIt->NonRepeatingShuttleLinkHeadCode = "";
18929  }
18930  }
18931  }
18932  //from here only TrainDataVectorCopy used
18933  SequenceLog += "1\n";
18934  //build AllServiceCallingLocsMap, it only uses the base service reference (with /1, /2 etc suffixes) as later times are calculated from the repeat number
18935  TServiceCallingLocsList ServiceCallingLocsList;
18936  std::pair<AnsiString, TServiceCallingLocsList> AllServiceCallingLocsEntry;
18937  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18938  {
18939  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18940  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18941  AllServiceCallingLocsEntry.first = TrainDataEntry.ServiceReference;
18942  ServiceCallingLocsList.clear();
18943  if(ActionVector.empty())
18944  {
18945  continue;
18946  }
18947  if(ActionVector.at(0).SignallerControl)
18948  {
18949  continue;
18950  }
18951  for(unsigned int z = 0; z < ActionVector.size(); z++)
18952  {
18953  TActionVectorEntry AVE = ActionVector.at(z);
18954  if(AVE.FormatType == StartNew)
18955  {
18956  if(AVE.LocationType == AtLocation) //located Snt
18957  {
18958  ServiceCallingLocsList.push_back(AVE.LocationName);
18959  }
18960  else //unlocated Snt (could be entering at continuation)
18961  {
18963  if(TE.ActiveTrackElementName != "")
18964  {
18965  ServiceCallingLocsList.push_back(TE.ActiveTrackElementName);
18966  }
18967  else
18968  {
18969  int HLoc = TE.HLoc;
18970  int VLoc = TE.VLoc;
18971  AnsiString HString;
18972  AnsiString VString;
18973  if(HLoc < 0)
18974  {
18975  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18976  }
18977  else
18978  {
18979  HString = AnsiString(HLoc);
18980  }
18981  if(VLoc < 0)
18982  {
18983  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18984  }
18985  else
18986  {
18987  VString = AnsiString(VLoc);
18988  }
18989  ServiceCallingLocsList.push_back(HString + '-' + VString);
18990  }
18991  }
18992  }
18993  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18994  {
18995  ServiceCallingLocsList.push_back(AVE.LocationName);
18996  }
18997  else if(AVE.FormatType == TimeLoc) //z must be > 0
18998  {
18999  if(ServiceCallingLocsList.back() != AVE.LocationName)
19000  {
19001  ServiceCallingLocsList.push_back(AVE.LocationName); //may be listed twice in succession so only want one entry
19002  }
19003  }
19004  else if(AVE.FormatType == PassTime)
19005  {
19006  ServiceCallingLocsList.push_back(AVE.LocationName);
19007  }
19008  else if(AVE.FormatType == TimeTimeLoc)
19009  {
19010  ServiceCallingLocsList.push_back(AVE.LocationName);
19011  }
19012  else if(AVE.Command == "cdt") //list if not next to start or finish
19013  {
19014  if(ActionVector.at(z-1).SequenceType == StartSequence)
19015  {
19016  continue;
19017  }
19018  else if(ActionVector.at(z+1).SequenceType == FinishSequence) //although deal with Fer entries cdt (train stopped) can't precede FER (train moving)
19019  {
19020  continue;
19021  }
19022  else
19023  {
19024  AnsiString TimeString = Utilities->Format96HHMM(AVE.EventTime);
19025  ServiceCallingLocsList.push_back("%%%" + TimeString); //%%% is a marker - unlikely that any locations will begin with this & easy to check to identify a time
19026  }
19027  }
19028  else if(AVE.FormatType == ExitRailway) //Fer
19029  {
19030  TTrackElement TE = Track->TrackElementAt(995, AVE.ExitList.front());
19031  AnsiString LName = TE.ActiveTrackElementName;
19032  if(LName != "")
19033  {
19034  ServiceCallingLocsList.push_back(LName);
19035  }
19036  else
19037  {
19038  int HLoc = TE.HLoc;
19039  int VLoc = TE.VLoc;
19040  AnsiString HString;
19041  AnsiString VString;
19042  if(HLoc < 0)
19043  {
19044  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19045  }
19046  else
19047  {
19048  HString = AnsiString(HLoc);
19049  }
19050  if(VLoc < 0)
19051  {
19052  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19053  }
19054  else
19055  {
19056  VString = AnsiString(VLoc);
19057  }
19058  ServiceCallingLocsList.push_back(HString + '-' + VString);
19059  }
19060  }
19061  }
19062  AllServiceCallingLocsEntry.second = ServiceCallingLocsList;
19063  AllServiceCallingLocsMap.insert(AllServiceCallingLocsEntry);
19064  }
19065  //AllServiceCallingLocsMap built
19066  SequenceLog += "2\n";
19067 /*
19068 // this sequence is to test the validity of AllServiceCallingLocsMap
19069  AnsiString TestFile = CurDir + "\\Formatted timetables\\TestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
19070  std::ofstream Test(TestFile.c_str());
19071 
19072  if(TestFile == 0)
19073  {
19074  ShowMessage("TestFile failed to open - can't be created");
19075  Utilities->CallLogPop();
19076  return false;
19077  }
19078 
19079  for(TAllServiceCallingLocsMap::iterator ASCLIt = AllServiceCallingLocsMap.begin(); ASCLIt != AllServiceCallingLocsMap.end(); ASCLIt++)
19080  {
19081  Test << ASCLIt->first << '\n'; //service ref
19082  for(TServiceCallingLocsList::iterator SCLIt = ASCLIt->second.begin(); SCLIt != ASCLIt->second.end(); SCLIt++)
19083  {
19084  Test << *SCLIt << '\n';
19085  }
19086  Test << "\n\n";
19087  }
19088  Test.close();
19089  Utilities->CallLogPop();
19090  return true;
19091 */
19092  //initialise variables before calc LastTTTime & build LocServiceTimesVector
19093  if(TrainDataVectorCopy.empty())
19094  {
19095  ShowMessage("Unable to create a program-readable timetable - please check the timetable file validity");
19096  Utilities->CallLogPop(2209);
19097  return(false);
19098  }
19099  TLocServiceTimes TLSTEntry;
19100  TLocServiceTimesVector LocServiceTimesVector; //will be on heap as TrainController is on the heap
19101  bool NumPlatsAtThisLocCalculated = false, ArrivalsPrinted = false, DeparturesPrinted = false, AtLocsPrinted = false;
19102  AnsiString PreviousService = "", PreviousServiceAndRepeatNumTotalOutput = "", BasicTime = "", MinuteString = "", LastAnsiTime = "";
19103  int NumTrains = 0, NumPlats = 0, LastFrhCount = 0, FrhCount = 0, NumTrainsAtLoc = 0;
19104  LastTTTime = "";
19105  SequenceLog += "3\n";
19106  //calculate LastTTTime
19107  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19108  {
19109  TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
19110  TActionVector &ActionVector = TrainDataEntry.ActionVector;
19111  TActionVectorIterator AVLast = ActionVector.end() - 1; //points to last entry
19112  TDateTime LastTDTime;
19113  int IncMinutes = 0;
19114  NumTrains = TrainDataEntry.NumberOfTrains;
19115  if(ActionVector.empty())
19116  {
19117  continue;
19118  }
19119  if(ActionVector.at(0).SignallerControl)
19120  {
19121  continue;
19122  }
19123  if(AVLast->FormatType == Repeat)
19124  {
19125  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
19126  AVLast--; //now points to the command before the repeat
19127  }
19128  if(AVLast->FormatType == FinRemHere) //not 'else if' as may have both a repeat and an Frh
19129  {
19130  AVLast--; //points to last timed entry
19131  }
19132  //here AVLast points to last entry with a time
19133  if(AVLast->ArrivalTime != TDateTime(-1))
19134  {
19135  LastTDTime = AVLast->ArrivalTime;
19136  }
19137  else if(AVLast->EventTime != TDateTime(-1)) //can't be a departure time
19138  {
19139  LastTDTime = AVLast->EventTime;
19140  }
19141  else
19142  {
19143  continue; //shouldn't ever reach here but if do then skip this service
19144  }
19145  if(NumTrains == 1)
19146  {
19147  LastAnsiTime = Utilities->Format96HHMM(LastTDTime);
19148  }
19149  else
19150  {
19151  LastAnsiTime = Utilities->Format96HHMM(GetRepeatTime(59, LastTDTime, NumTrains - 1, IncMinutes));
19152  }
19153  if(LastAnsiTime > LastTTTime)
19154  {
19155  LastTTTime = LastAnsiTime;
19156  }
19157  }
19158  SequenceLog += "4\n";
19159 //build LocServiceTimesVector
19160 
19161 /*
19162  struct TLocServiceTimes
19163  {
19164  AnsiString Location;
19165  AnsiString ServiceAndRepeatNum;
19166  AnsiString AtLocTime;
19167  AnsiString ArrTime;
19168  AnsiString DepTime;
19169  AnsiString FrhMarker;
19170  };
19171  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
19172 
19173 This works as follows:
19174 ServiceAndRepeatNum is taken from the TrainDataVectorCopy as it is the same for all actionvector entries
19175 Location is taken from ActionVectorEntry.LocationName if there is one, or from the H & V locations if not (e.g. at an unnamed Fer)
19176 AtLocTime is always entered either on its own or with ArrTime or DepTime as appropriate
19177 
19178 Every action for every train is examined and times entered as follows:-
19179 a) a located Snt: entry time becomes the AtLocTime, and all subsequent minutes entered too up to but not including a departure or a finish
19180 b) an unlocated Snt: entry time becomes DepTime
19181 c) all other start entries: entry time becomes AtLoc, and all subsequent minutes entered too up to but not including a departure or a finish
19182 d) TimeLoc Arr: entry time becomes ArrTime, and all subsequent minutes entered too up to but not including a departure or a finish
19183 e) TimeLoc Dep: entry time becomes DepTime, checks if DepTime same as earlier ArrTime and if so all times go in as one entry
19184 f) TimeTimeLoc: Arrival time entered as ArrTime, a check if Arr & Dep same and if so go in as one entry, else all minutes between entered as AtLocs then DepTime
19185 g) ExitRailway (Fer): check if located and use LocationName if so. else use H & V positions, time becomes AtLocTime
19186 h) Frh: use the earlier vector time as the AtLocTime and set FrhMarker, and enter all minutes to end of timetable as AtLocs
19187 i) Frh-sh: for the last train use time as AtLocTime, set FrhMarker, and enter all minutes to end of timetable as AtLocs
19188 j) all other finish entries (all link to another service) are ignored as will be listed for the linked service
19189 */
19190  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19191  {
19192  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
19193  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
19194  AnsiString ServiceRef = TrainDataEntry.ServiceReference;
19195  int IncMinutes = 0;
19196  NumTrains = TrainDataEntry.NumberOfTrains;
19197  if(ActionVector.empty())
19198  {
19199  continue;
19200  }
19201  if(ActionVector.at(0).SignallerControl)
19202  {
19203  continue;
19204  }
19205  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
19206  {
19207  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
19208  }
19209  for(int y = 0; y < NumTrains; y++) //y is the repeat number
19210  {
19211  if(NumTrains == 1)
19212  {
19213  TLSTEntry.ServiceAndRepeatNum = ServiceRef;
19214  }
19215  else if(y == 0)
19216  {
19217  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (First service)";
19218  }
19219  else
19220  {
19221  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (Repeat " + AnsiString(y) + ")";
19222  }
19223  for(unsigned int z = 0; z < ActionVector.size(); z++)
19224  {
19225  TActionVectorEntry AVE = ActionVector.at(z);
19226  TLSTEntry.AtLocTime = "";
19227  TLSTEntry.ArrTime = "";
19228  TLSTEntry.DepTime = "";
19229  TLSTEntry.Location = "";
19230  TLSTEntry.FrhMarker = "";
19231 
19232  if(AVE.FormatType == StartNew) //Snt only
19233  {
19234  if(AVE.LocationType == AtLocation) //located Snt, class time as AtLocTime
19235  {
19236  TLSTEntry.Location = AVE.LocationName;
19237  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(58, AVE.EventTime, y, IncMinutes));
19238  LocServiceTimesVector.push_back(TLSTEntry);
19239 
19240  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19241  AnsiString IncTime = "", FoundStopTime = ""; //these handled in later checks
19242  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19243  {
19244  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19245  {
19246  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(62, ActionVector.at(a).DepartureTime, y, IncMinutes));
19247  break;
19248  }
19249  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
19250  {
19251  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(63, ActionVector.at(a).EventTime, y, IncMinutes));
19252  break;
19253  }
19254  }
19255  if(FoundStopTime == "")
19256  {
19257  throw Exception("Failure to determine FoundStopTime for located Snt");
19258  }
19259  int WhileCount = 0;
19260  while(true)
19261  {
19262  //add minutes until reach FoundStopTime but don't add that time
19263  WhileCount++;
19264  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19265  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19266  TLSTEntry.DepTime = "";
19267  TLSTEntry.ArrTime = "";
19268  if(IncTime >= FoundStopTime) //don't add that time
19269  {
19270  break;
19271  }
19272  LocServiceTimesVector.push_back(TLSTEntry);
19273  if(WhileCount > 2000)
19274  {
19275  throw Exception("While loop failed to break in 2000 loops for located Snt");
19276  }
19277  }
19278  }
19279  else //unlocated Snt, use the EventTime as DepTime for this vector
19280  {
19282  if(TE.ActiveTrackElementName != "")
19283  {
19284  TLSTEntry.Location = TE.ActiveTrackElementName;
19285  }
19286  else
19287  {
19288  int HLoc = TE.HLoc;
19289  int VLoc = TE.VLoc;
19290  AnsiString HString;
19291  AnsiString VString;
19292  if(HLoc < 0)
19293  {
19294  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19295  }
19296  else
19297  {
19298  HString = AnsiString(HLoc);
19299  }
19300  if(VLoc < 0)
19301  {
19302  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19303  }
19304  else
19305  {
19306  VString = AnsiString(VLoc);
19307  }
19308  TLSTEntry.Location = HString + '-' + VString;
19309  }
19310  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(49, AVE.EventTime, y, IncMinutes));
19311  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
19312  LocServiceTimesVector.push_back(TLSTEntry);
19313  }
19314  }
19315 
19316  else if(AVE.SequenceType == StartSequence) //other start entries, all located
19317  {
19318  TLSTEntry.Location = AVE.LocationName;
19319  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(50, AVE.EventTime, y, IncMinutes));
19320  LocServiceTimesVector.push_back(TLSTEntry);
19321  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19322  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
19323  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19324  {
19325  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19326  {
19327  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(64, ActionVector.at(a).DepartureTime, y, IncMinutes));
19328  break;
19329  }
19330  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
19331  {
19332  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(65, ActionVector.at(a).EventTime, y, IncMinutes));
19333  break;
19334  }
19335  }
19336  if(FoundStopTime == "")
19337  {
19338  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
19339  }
19340  int WhileCount = 0;
19341  while(true)
19342  {
19343  //add minutes until reach FoundStopTime but don't add that time
19344  WhileCount++;
19345  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19346  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19347  TLSTEntry.DepTime = "";
19348  TLSTEntry.ArrTime = "";
19349  if(IncTime >= FoundStopTime) //don't add that time
19350  {
19351  break;
19352  }
19353  LocServiceTimesVector.push_back(TLSTEntry);
19354  if(WhileCount > 2000)
19355  {
19356  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
19357  }
19358  }
19359  }
19360 
19361  else if(AVE.FormatType == TimeLoc) //could be arr or dep, if arrival add in all mins to the departure or finish
19362  {
19363  TLSTEntry.Location = AVE.LocationName;
19364  if(AVE.ArrivalTime > TDateTime(-1)) //one or other set, not both, in this case arrival
19365  {
19366  bool SkipAddingMinutes = false;
19367  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(51, AVE.ArrivalTime, y, IncMinutes));
19368  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
19369  LocServiceTimesVector.push_back(TLSTEntry); //Arr and AtLoc added (may be popped if dep time found to be same at next TimeLoc)
19370  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19371  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
19372  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19373  {
19374  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19375  {
19376  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(66, ActionVector.at(a).DepartureTime, y, IncMinutes));
19377  break;
19378  }
19379  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered for in a later test
19380  {
19381  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(67, ActionVector.at(a).EventTime, y, IncMinutes));
19382  if((a <= (z + 2)) && (FoundStopTime == TLSTEntry.ArrTime) && ((ActionVector.at(a).LinkedTrainEntryPtr != NULL) || (ActionVector.at(a).NonRepeatingShuttleLinkEntryPtr != NULL)))
19383  //finish immediately after arrival at same time, and a forward linked service. Added at v2.6.0 to prevent two linked trains being listed at same location
19384  //at v2.10.0 changed (a == z + 1) to (a <= (z + 2)) as can have a cdt between, this allows for that
19385  {
19386  LocServiceTimesVector.pop_back(); //pop the entry as the linked train will be listed at the relevant time and don't want to list both
19387  SkipAddingMinutes = true;
19388  }
19389  break;
19390  }
19391  }
19392  if(FoundStopTime == "")
19393  {
19394  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
19395  }
19396  if(!SkipAddingMinutes)
19397  {
19398  int WhileCount = 0;
19399  while(true)
19400  {
19401  //add minutes until reach FoundStopTime but don't add that time
19402  WhileCount++;
19403  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19404  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19405  TLSTEntry.DepTime = "";
19406  TLSTEntry.ArrTime = "";
19407  if(IncTime >= FoundStopTime) //don't add that time
19408  {
19409  break;
19410  }
19411  LocServiceTimesVector.push_back(TLSTEntry);
19412  if(WhileCount > 2000)
19413  {
19414  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
19415  }
19416  }
19417  }
19418  }
19419  else if(AVE.DepartureTime > TDateTime(-1)) //need to check if the arrival time (which should already be listed) is same and if so put all times on one line
19420  {
19421  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(52, AVE.DepartureTime, y, IncMinutes));
19422  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
19423  if((TLSTEntry.Location == LocServiceTimesVector.back().Location) && (TLSTEntry.ServiceAndRepeatNum == LocServiceTimesVector.back().ServiceAndRepeatNum)) //if not it's a new service
19424  {
19425  if(TLSTEntry.DepTime == LocServiceTimesVector.back().ArrTime)
19426  {
19427  TLSTEntry.ArrTime = LocServiceTimesVector.back().ArrTime;
19428  LocServiceTimesVector.pop_back();
19429  LocServiceTimesVector.push_back(TLSTEntry); //Arr, Dep and AtLoc added in place of earlier Arr entry.
19430  }
19431  else //just add the dep & atloc times
19432  {
19433  TLSTEntry.ArrTime = "";
19434  LocServiceTimesVector.push_back(TLSTEntry);
19435  }
19436  }
19437  else //just add the dep & atloc times
19438  {
19439  TLSTEntry.ArrTime = "";
19440  LocServiceTimesVector.push_back(TLSTEntry);
19441  }
19442  }
19443  }
19444 
19445  else if(AVE.FormatType == TimeTimeLoc)
19446  {
19447  TLSTEntry.Location = AVE.LocationName;
19448  if(AVE.ArrivalTime > TDateTime(-1)) //should be
19449  {
19450  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(53, AVE.ArrivalTime, y, IncMinutes));
19451  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
19452  }
19453  if(AVE.DepartureTime > TDateTime(-1)) //should be
19454  {
19455  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(54, AVE.DepartureTime, y, IncMinutes));
19456  }
19457  if(TLSTEntry.ArrTime == TLSTEntry.DepTime)
19458  {
19459  LocServiceTimesVector.push_back(TLSTEntry);
19460  }
19461  else
19462  {
19463  AnsiString TempDepTime = TLSTEntry.DepTime; //save it temporarily
19464  TLSTEntry.DepTime = "";
19465  LocServiceTimesVector.push_back(TLSTEntry); //push just the arrival and AtLoc times
19466  TLSTEntry.ArrTime = ""; //done with this now
19467  while(TLSTEntry.AtLocTime < TempDepTime)
19468  {
19469  TLSTEntry.AtLocTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19470  if(TLSTEntry.AtLocTime == TempDepTime)
19471  {
19472  TLSTEntry.DepTime = TempDepTime; //restore value
19473  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc and Dep times - will finish loop after this
19474  }
19475  else
19476  {
19477  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc time on its own
19478  }
19479  }
19480  }
19481  }
19482 
19483  else if(AVE.FormatType == PassTime) //added at v2.9.1
19484  { //adds 2 entries, 1st with PassTime as ArrTime and AtLocTime, 2nd with PassTime as AtLocTime & DepTime
19485  TLSTEntry.Location = AVE.LocationName;;
19486  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(73, AVE.EventTime, y, IncMinutes));
19487  TLSTEntry.ArrTime = TLSTEntry.AtLocTime; //DepTime already set to null
19488  LocServiceTimesVector.push_back(TLSTEntry); //1st entry
19489  TLSTEntry.ArrTime = ""; //need to reset this to null
19490  TLSTEntry.DepTime = TLSTEntry.AtLocTime;
19491  LocServiceTimesVector.push_back(TLSTEntry); //2nd entry
19492  }
19493 
19494  else if(AVE.FormatType == ExitRailway) //Fer
19495  {
19496  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(77, AVE.EventTime, y, IncMinutes)); //need this as arrival time so arrival analysis works
19497  //properly for exits (continuation entries use dep time so ok)
19498  //added at v2.20.3
19499  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(55, AVE.EventTime, y, IncMinutes));
19500  //don't know which exit will be used during operation so use the first in ExitList, if several with different names then will
19501  //be wrong, but can't guess from here & most will have same name
19502  AnsiString LName = Track->TrackElementAt(990, AVE.ExitList.front()).ActiveTrackElementName;
19503  if(LName != "")
19504  {
19505  TLSTEntry.Location = LName;
19506  }
19507  else
19508  {
19509  int HLoc = Track->TrackElementAt(991, AVE.ExitList.front()).HLoc;
19510  int VLoc = Track->TrackElementAt(992, AVE.ExitList.front()).VLoc;
19511  AnsiString HString;
19512  AnsiString VString;
19513  if(HLoc < 0)
19514  {
19515  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19516  }
19517  else
19518  {
19519  HString = AnsiString(HLoc);
19520  }
19521  if(VLoc < 0)
19522  {
19523  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19524  }
19525  else
19526  {
19527  VString = AnsiString(VLoc);
19528  }
19529  TLSTEntry.Location = HString + '-' + VString;
19530  }
19531  LocServiceTimesVector.push_back(TLSTEntry); //just use the exit time as AtLocTime
19532  }
19533 
19534  else if(AVE.FormatType == FinRemHere) //Frh, not Frh-sh, that dealt with next
19535  {
19536  AnsiString FrhTime;
19537  if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
19538  {
19539  FrhTime = Utilities->Format96HHMM(GetRepeatTime(56, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
19540  }
19541  else if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
19542  {
19543  FrhTime = Utilities->Format96HHMM(GetRepeatTime(57, ActionVector.at(z - 1).EventTime, y, IncMinutes));
19544  }
19545  TLSTEntry.AtLocTime = FrhTime; //use the last entry time as the first recorded time
19546  TLSTEntry.Location = AVE.LocationName;
19547  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19548  TLSTEntry.FrhMarker = "Frh";
19549  LocServiceTimesVector.push_back(TLSTEntry);
19550  TLSTEntry.FrhMarker = "";
19551  //add all times from next minute to end of timetable
19552  while(IncTime <= LastTTTime)
19553  {
19554  TLSTEntry.AtLocTime = IncTime;
19555  LocServiceTimesVector.push_back(TLSTEntry);
19556  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19557  }
19558  }
19559 
19560  else if(AVE.Command == "Frh-sh") //do nothing if links to other shuttle but treat as Frh when remaining here
19561  {
19562  if(y == NumTrains - 1) //last repeat, it remains here when accessed for the last train
19563  {
19564  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(68, AVE.EventTime, y, IncMinutes));
19565  TLSTEntry.Location = AVE.LocationName;
19566  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19567  TLSTEntry.FrhMarker = "Frh";
19568  LocServiceTimesVector.push_back(TLSTEntry);
19569  TLSTEntry.FrhMarker = "";
19570  //add all times from next minute to end of timetable
19571  while(IncTime <= LastTTTime)
19572  {
19573  TLSTEntry.AtLocTime = IncTime;
19574  LocServiceTimesVector.push_back(TLSTEntry);
19575  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19576  }
19577  }
19578  }
19579 
19580  else if(AVE.SequenceType == FinishSequence) //other finish types - all located & all link to another service
19581  {
19582  //nothing is done here as the entry will be listed at this time under the new service reference
19583  }
19584  }
19585  }
19586  }
19587  SequenceLog += "5\n";
19588  //now sort in location order
19589  std::sort(LocServiceTimesVector.begin(), LocServiceTimesVector.end(), &LocServiceTimesLocationSort); //LocServiceTimesLocationSort is a function pointer
19590  //LocServiceTimesVector now complete & sorted in location order
19591 
19592 /*
19593 //start of debugging section
19594 //create LocServiceTimesVector output file for debugging purposes
19595  AnsiString LSTVTestFile = CurDir + "\\Formatted timetables\\LSTVTestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
19596  std::ofstream LSTVFile(LSTVTestFile.c_str());
19597  for(TLocServiceTimesVector::iterator LSTVIt = LocServiceTimesVector.begin(); LSTVIt != LocServiceTimesVector.end(); LSTVIt++)
19598  {
19599  LSTVFile << LSTVIt->Location + '\n';
19600  LSTVFile << LSTVIt->ServiceAndRepeatNum + '\n';
19601  LSTVFile << "AtLocTime = " + LSTVIt->AtLocTime + '\n';
19602  LSTVFile << "ArrTime = " + LSTVIt->ArrTime + '\n';
19603  LSTVFile << "DepTime = " + LSTVIt->DepTime + '\n';
19604  if(LSTVIt->FrhMarker == "")
19605  {
19606  LSTVFile << "Not Frh\n";
19607  }
19608  else
19609  {
19610  LSTVFile << LSTVIt->FrhMarker + '\n';
19611  }
19612  LSTVFile << '\n';
19613  }
19614  LSTVFile.close();
19615  Utilities->CallLogPop();
19616  return(true);
19617 //end of debugging section
19618 */
19619  //declare pointers for use in printouts
19620  TLocServiceTimesVector::iterator Ptr1, Ptr2;
19621 
19622  //set up the output file
19623  AnsiString TTFileName3 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
19624  TTFileName3 = CurDir + "\\Formatted timetables\\Conflict Analysis " + TTFileName3 + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
19625 
19626  std::ofstream TTFile3(TTFileName3.c_str());
19627 
19628  if(TTFile3 == 0)
19629  {
19630  ShowMessage("Conflict Analysis file failed to open - can't be created");
19631  Utilities->CallLogPop(2210);
19632  return(false);
19633  }
19634  if(LocServiceTimesVector.empty())
19635  {
19636  ShowMessage("No timetabled services found");
19637  TTFile3.close();
19638  DeleteFile(TTFileName3);
19639  Utilities->CallLogPop(2211);
19640  return(false);
19641  }
19642  TTFile3 << "Timetable analysis for timetable: '" + TimetableTitle + "' in conjunction with railway: '" + RailwayTitle + "'\n";
19643  TTFile3 << "See user manual or on-screen help section 5.12 for detailed information.\n\n\n";
19644  SequenceLog += "6\n";
19645 
19646 /*
19647 //print out TrainDataVectorCopy & TrainDataVector for debugging purposes, TrainDataVectorCopy first
19648 
19649 // Double crosslink (shuttle) table:
19650 //Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
19651 // Code ShuttleLink- EntryPtr ShuttleLink-
19652 // HeadCode EntryPtr
19653 
19654 //Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
19655 //Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
19656 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19657 //Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
19658 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19659 //Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
19660 //
19661 //Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
19662 
19663 //NOTE: from above for F-nshs & Sns-fsh in the TrainDataVectorCopy the OtherLinkedHeadCode will be correct as it is derived from LinkTrainEntryPtr which is correct
19664 //but for the original TrainDataVector the OtherLinkedHeadCode will be incorrect, hence have to use the NonRepeatingShuttleLinkHeadCode as that is the correct one
19665 //these were errors when first coded but work ok, just keep in mind when making any changes
19666 
19667 std::ofstream TDVCFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVectorCopy.txt").c_str());
19668 AnsiString OHC = "", NRHC = "";
19669 AnsiString OLk = "", NRLk = "";
19670 TDVCFile << "Note that for the TrainDataVectorCopy OH and OLk should always be the same, as OH is derived from OHLk; and similarly for NR and NRLk\n\n";
19671 for(TTrainDataVector::iterator TDVCIt = TrainDataVectorCopy.begin(); TDVCIt != TrainDataVectorCopy.end(); TDVCIt++)
19672 {
19673  TDVCFile << TDVCIt->ServiceReference + '\n';
19674  TDVCFile << TDVCIt->Description + '\n';
19675  for(unsigned int x = 0; x < TDVCIt->ActionVector.size(); x++)
19676  {
19677  TActionVectorEntry AVE = TDVCIt->ActionVector.at(x);
19678  if(AVE.OtherHeadCode == "")
19679  {
19680  OHC = "OH 0";
19681  }
19682  else
19683  {
19684  OHC = "OH " + AVE.OtherHeadCode;
19685  }
19686  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19687  {
19688  NRHC = "NR 0";
19689  }
19690  else
19691  {
19692  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19693  }
19694  if(TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19695  {
19696  OLk = "OLk 0";
19697  }
19698  else
19699  {
19700  OLk = "OLk " + TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19701  }
19702  if(TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19703  {
19704  NRLk = "NRLk 0";
19705  }
19706  else
19707  {
19708  NRLk = "NRLk " + TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19709  }
19710 
19711  if(AVE.FormatType == TimeCmd) //cdt only
19712  {
19713  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19714  }
19715  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19716  {
19717  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19718  }
19719  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19720  {
19721  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19722  }
19723  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19724  {
19725  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19726  }
19727  else if(AVE.FormatType == StartNew)
19728  {
19729  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-1, AVE.RearStartOrRepeatMins).ElementID
19730  << " FrontStartID " << Track->TrackElementAt(-2, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19731  }
19732  else if(AVE.FormatType == SNTShuttle)
19733  {
19734  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-3, AVE.RearStartOrRepeatMins).ElementID
19735  << " FrontStartID " << Track->TrackElementAt(-4, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19736  }
19737  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19738  {
19739  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19740  }
19741  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19742  {
19743  TDVCFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19744  }
19745  else if(AVE.FormatType == TimeTimeLoc)
19746  {
19747  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19748  }
19749  else if(AVE.FormatType == PassTime)
19750  {
19751  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19752  }
19753  else if(AVE.FormatType == ExitRailway)
19754  {
19755  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19756  }
19757  else if(AVE.FormatType == FinRemHere)
19758  {
19759  TDVCFile << "Frh" << '\n';
19760  }
19761  }
19762  TDVCFile << '\n';
19763 }
19764 TDVCFile.close();
19765 
19766 //print out original TrainDataVector for comparison
19767 std::ofstream TDVFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVector.txt").c_str());
19768 TDVFile << "Note that in the TrainDataVector, non-repeating shuttle link services F-nshs and Sns-fsh use the non-repeating headcode (NR) values for the corresponding "
19769  "shuttle headcodes when it should be the other headcode (OH), and the other headcode is unused. The link values are the right way round. Also OH & NR "
19770  "values ARE headcodes and not service references, but OLk and NRLk values are service references.\n\n";
19771 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19772 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19773 for(TTrainDataVector::iterator TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end(); TDVIt++)
19774 {
19775  TDVFile << TDVIt->ServiceReference + '\n';
19776  TDVFile << TDVIt->Description + '\n';
19777  for(unsigned int x = 0; x < TDVIt->ActionVector.size(); x++)
19778  {
19779  TActionVectorEntry AVE = TDVIt->ActionVector.at(x);
19780  if(AVE.OtherHeadCode == "")
19781  {
19782  OHC = "OH 0";
19783  }
19784  else
19785  {
19786  OHC = "OH " + AVE.OtherHeadCode;
19787  }
19788  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19789  {
19790  NRHC = "NR 0";
19791  }
19792  else
19793  {
19794  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19795  }
19796  if(TDVIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19797  {
19798  OLk = "OLk 0";
19799  }
19800  else
19801  {
19802  OLk = "OLk " + TDVIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19803  }
19804  if(TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19805  {
19806  NRLk = "NRLk 0";
19807  }
19808  else
19809  {
19810  NRLk = "NRLk " + TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19811  }
19812 
19813  if(AVE.FormatType == TimeCmd) //cdt only
19814  {
19815  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19816  }
19817  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19818  {
19819  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19820  }
19821  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19822  {
19823  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19824  }
19825  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19826  {
19827  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19828  }
19829  else if(AVE.FormatType == StartNew)
19830  {
19831  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-5, AVE.RearStartOrRepeatMins).ElementID
19832  << " FrontStartID " << Track->TrackElementAt(-6, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19833  }
19834  else if(AVE.FormatType == SNTShuttle)
19835  {
19836  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-7, AVE.RearStartOrRepeatMins).ElementID
19837  << " FrontStartID " << Track->TrackElementAt(-8, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19838  }
19839  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19840  {
19841  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19842  }
19843  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19844  {
19845  TDVFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19846  }
19847  else if(AVE.FormatType == TimeTimeLoc)
19848  {
19849  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19850  }
19851  else if(AVE.FormatType == PassTime)
19852  {
19853  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19854  }
19855  else if(AVE.FormatType == ExitRailway)
19856  {
19857  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19858  }
19859  else if(AVE.FormatType == FinRemHere)
19860  {
19861  TDVFile << "Frh" << '\n';
19862  }
19863  }
19864  TDVFile << '\n';
19865 }
19866 TDVFile.close();
19867 //end of debugging
19868 */
19869  //arrivals
19870  if(ArrChecked)
19871  {
19872  //sort in ArrTime order for each location
19873  Ptr1 = LocServiceTimesVector.begin();
19874  Ptr2 = Ptr1 + 1;
19875  while(Ptr2 != LocServiceTimesVector.end())
19876  {
19877  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19878  {
19879  Ptr2++;
19880  if(Ptr2 == LocServiceTimesVector.end())
19881  {
19882  break;
19883  }
19884  }
19885  std::sort(Ptr1, Ptr2, &LocServiceTimesArrTimeSort);
19886  Ptr1 = Ptr2; //first entry with next name
19887  if(Ptr2 != LocServiceTimesVector.end())
19888  {
19889  Ptr2++;
19890  }
19891  }
19892 
19893  //routine for arrivals - number of trains arriving within the specified range with services listed at the end
19894 
19895  TTFile3 << "Arrival & pass analysis: an asterisk means that the number of same approach code arrivals and passes is equal to or greater than the number of platforms.\n";
19896  TTFile3 << "If the total number of arrivals and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19897  MinuteString = " minutes";
19898  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19899  if(ArrRange == 1)
19900  {
19901  MinuteString = " minute";
19902  }
19903  TTFile3 << "Location,Number of,Number of,Services arriving within " << AnsiString(ArrRange) << MinuteString << " with their arrival times and approach codes\n";
19904  TTFile3 << ",Platforms,Trains\n\n";
19905 
19906  Ptr1 = LocServiceTimesVector.begin();
19907  Ptr2 = Ptr1 + 1;
19908  while(Ptr2 != LocServiceTimesVector.end())
19909  {
19910  PreviousService = "";
19911  NumTrainsAtLoc = 0;
19912  ServiceAndRepeatNumTotal = "";
19913  NumPlats = 0;
19914  NumPlatsAtThisLocCalculated = false;
19915  BasicTime = "";
19916  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19917  {
19918  PreviousService = "";
19919  NumTrainsAtLoc = 0;
19920  ServiceAndRepeatNumTotal = "";
19921  NumPlats = 0;
19922  NumPlatsAtThisLocCalculated = false;
19923  BasicTime = "";
19924  Ptr1++;
19925  Ptr2++;
19926  if(Ptr2 == LocServiceTimesVector.end())
19927  {
19928  break;
19929  }
19930  }
19931  if(Ptr2 == LocServiceTimesVector.end())
19932  {
19933  break;
19934  }
19935  while(Ptr2->Location == Ptr1->Location)
19936  {
19937  PreviousService = "";
19938  NumTrainsAtLoc = 0;
19939  ServiceAndRepeatNumTotal = "";
19940  BasicTime = Ptr1->ArrTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19941  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19942  {
19943  break;
19944  }
19945  while(!WithinTimeRange(0, BasicTime, Ptr2->ArrTime, ArrRange) || ((Ptr1->ArrTime == "") && (Ptr2->ArrTime == "")))
19946  {
19947  BasicTime = Ptr2->ArrTime; //used to compare later times or last can exceed first
19948  Ptr1++;
19949  Ptr2++;
19950  if(Ptr2 == LocServiceTimesVector.end())
19951  {
19952  break;
19953  }
19954  if(Ptr2->Location != Ptr1->Location)
19955  {
19956  break;
19957  }
19958  }
19959  if(Ptr2 == LocServiceTimesVector.end())
19960  {
19961  break;
19962  }
19963  if(Ptr2->Location != Ptr1->Location)
19964  {
19965  break;
19966  }
19967  while(WithinTimeRange(1, BasicTime, Ptr2->ArrTime, ArrRange))
19968  {
19969  if((Ptr1->ArrTime == "") && (Ptr2->ArrTime == ""))
19970  {
19971  break;
19972  }
19973  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19974  {
19975  NumPlats = Track->NumberOfPlatforms(0, Ptr1->Location);
19976  NumPlatsAtThisLocCalculated = true;
19977  }
19978  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19979  {
19980  if(ServiceAndRepeatNumTotal == "")
19981  {
19982  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19983  NumTrainsAtLoc = 1;
19984  }
19985  else
19986  {
19987  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19988  }
19989  }
19990  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19991  if(ServiceAndRepeatNumTotal == "")
19992  {
19993  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19994  NumTrainsAtLoc = 1;
19995  }
19996  else
19997  {
19998  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19999  }
20000  Ptr1 = Ptr2;
20001  Ptr2++;
20002  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(2, BasicTime, Ptr2->ArrTime, ArrRange)))
20003  {
20004  int MaxNumberOfSameDirections = 0;
20005  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, true, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
20006  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
20007  {
20008 // ShowMessage("Error in arrival analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
20009  TTFile3.close();
20010  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
20011 // Utilities->CallLogPop(2224);
20012 // return false;
20013  }
20014  AnsiString Asterisk = "";
20015  if(MaxNumberOfSameDirections >= NumPlats)
20016  {
20017  Asterisk = "* ";
20018  }
20019  //print out a single line for number of trains at loc with all service refs
20020  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
20021  ArrivalsPrinted = true;
20022  ServiceAndRepeatNumTotal = "";
20023  }
20024  if(Ptr2 == LocServiceTimesVector.end())
20025  {
20026  break;
20027  }
20028  if(Ptr2->Location != Ptr1->Location)
20029  {
20030  break;
20031  }
20032  }
20033  if(Ptr2 == LocServiceTimesVector.end())
20034  {
20035  break;
20036  }
20037  }
20038  }
20039  if(!ArrivalsPrinted)
20040  {
20041  TTFile3 << "Nothing to report for arrivals";
20042  }
20043  TTFile3 << "\n\n";
20044  }
20045  //end of routine for arrivals
20046  SequenceLog += "7\n";
20047  //departures
20048  if(DepChecked)
20049  {
20050  //sort in DepTime order for each location
20051  Ptr1 = LocServiceTimesVector.begin();
20052  Ptr2 = Ptr1 + 1;
20053  while(Ptr2 != LocServiceTimesVector.end())
20054  {
20055  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
20056  {
20057  Ptr2++;
20058  if(Ptr2 == LocServiceTimesVector.end())
20059  {
20060  break;
20061  }
20062  }
20063  std::sort(Ptr1, Ptr2, &LocServiceTimesDepTimeSort);
20064  Ptr1 = Ptr2; //first entry with next name
20065  if(Ptr2 != LocServiceTimesVector.end())
20066  {
20067  Ptr2++;
20068  }
20069  }
20070 
20071  //routine for departures - number of trains departing within the specified range with services listed at the end
20072  TTFile3 << "Departure & pass analysis: an asterisk means that the number of same exit code departures and passes is equal to or greater than the number of platforms.\n";
20073  TTFile3 << "If the total number of departures and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
20074  MinuteString = " minutes";
20075  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
20076  if(DepRange == 1)
20077  {
20078  MinuteString = " minute";
20079  }
20080  TTFile3 << "Location,Number of,Number of,Services departing within " << AnsiString(DepRange) << MinuteString << " with their departure times and exit codes\n";
20081  TTFile3 << ",Platforms,Trains\n\n";
20082 
20083  Ptr1 = LocServiceTimesVector.begin();
20084  Ptr2 = Ptr1 + 1;
20085  while(Ptr2 != LocServiceTimesVector.end())
20086  {
20087  PreviousService = "";
20088  NumTrainsAtLoc = 0;
20089  ServiceAndRepeatNumTotal = "";
20090  NumPlats = 0;
20091  NumPlatsAtThisLocCalculated = false;
20092  BasicTime = "";
20093  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
20094  {
20095  PreviousService = "";
20096  NumTrainsAtLoc = 0;
20097  ServiceAndRepeatNumTotal = "";
20098  NumPlats = 0;
20099  NumPlatsAtThisLocCalculated = false;
20100  BasicTime = "";
20101  Ptr1++;
20102  Ptr2++;
20103  if(Ptr2 == LocServiceTimesVector.end())
20104  {
20105  break;
20106  }
20107  }
20108  if(Ptr2 == LocServiceTimesVector.end())
20109  {
20110  break;
20111  }
20112  while(Ptr2->Location == Ptr1->Location)
20113  {
20114  PreviousService = "";
20115  NumTrainsAtLoc = 0;
20116  ServiceAndRepeatNumTotal = "";
20117  BasicTime = Ptr1->DepTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
20118  if((Ptr1->Location == "") && (Ptr2->Location == ""))
20119  {
20120  break;
20121  }
20122  while(!WithinTimeRange(3, BasicTime, Ptr2->DepTime, DepRange) || ((Ptr1->DepTime == "") && (Ptr2->DepTime == "")))
20123  {
20124  BasicTime = Ptr2->DepTime; //used to compare later times or last can exceed first
20125  Ptr1++;
20126  Ptr2++;
20127  if(Ptr2 == LocServiceTimesVector.end())
20128  {
20129  break;
20130  }
20131  if(Ptr2->Location != Ptr1->Location)
20132  {
20133  break;
20134  }
20135  }
20136  if(Ptr2 == LocServiceTimesVector.end())
20137  {
20138  break;
20139  }
20140  if(Ptr2->Location != Ptr1->Location)
20141  {
20142  break;
20143  }
20144  while(WithinTimeRange(4, BasicTime, Ptr2->DepTime, DepRange))
20145  {
20146  if((Ptr1->DepTime == "") && (Ptr2->DepTime == ""))
20147  {
20148  break;
20149  }
20150  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
20151  {
20152  NumPlats = Track->NumberOfPlatforms(1, Ptr1->Location);
20153  NumPlatsAtThisLocCalculated = true;
20154  }
20155  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
20156  {
20157  if(ServiceAndRepeatNumTotal == "")
20158  {
20159  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
20160  NumTrainsAtLoc = 1;
20161  }
20162  else
20163  {
20164  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
20165  }
20166  }
20167  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
20168  if(ServiceAndRepeatNumTotal == "")
20169  {
20170  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
20171  NumTrainsAtLoc = 1;
20172  }
20173  else
20174  {
20175  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
20176  }
20177  Ptr1 = Ptr2;
20178  Ptr2++;
20179  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(5, BasicTime, Ptr2->DepTime, DepRange)))
20180  {
20181  int MaxNumberOfSameDirections = 0;
20182  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(3, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, false, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
20183  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
20184  {
20185 // ShowMessage("Error in departure analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
20186  TTFile3.close();
20187  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
20188 // Utilities->CallLogPop(2225);
20189 // return false;
20190  }
20191  AnsiString Asterisk = "";
20192  if(MaxNumberOfSameDirections >= NumPlats)
20193  {
20194  Asterisk = "* ";
20195  }
20196  //print out a single line for number of trains at loc with all service refs
20197  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
20198  DeparturesPrinted = true;
20199  ServiceAndRepeatNumTotal = "";
20200  }
20201  if(Ptr2 == LocServiceTimesVector.end())
20202  {
20203  break;
20204  }
20205  if(Ptr2->Location != Ptr1->Location)
20206  {
20207  break;
20208  }
20209  }
20210  if(Ptr2 == LocServiceTimesVector.end())
20211  {
20212  break;
20213  }
20214  }
20215  }
20216  if(!DeparturesPrinted)
20217  {
20218  TTFile3 << "Nothing to report for departures";
20219  }
20220  TTFile3 << "\n\n";
20221  }
20222  //end of routine for departures
20223  SequenceLog += "8\n";
20224 
20225  //list trains at locations at same time
20226 
20227  if(AtLocChecked)
20228  {
20229  //sort in AtLocTime order for each location
20230  Ptr1 = LocServiceTimesVector.begin();
20231  Ptr2 = Ptr1 + 1;
20232  while(Ptr2 != LocServiceTimesVector.end())
20233  {
20234  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
20235  {
20236  Ptr2++;
20237  if(Ptr2 == LocServiceTimesVector.end())
20238  {
20239  break;
20240  }
20241  }
20242  std::sort(Ptr1, Ptr2, &LocServiceTimesAtLocTimeSort);
20243  Ptr1 = Ptr2; //first entry with next name
20244  if(Ptr2 != LocServiceTimesVector.end())
20245  {
20246  Ptr2++;
20247  }
20248  }
20249 
20250  //print out simultaneous AtLocs (don't need range of times for AtLocs)
20251  TTFile3 << "Trains present at location analysis: an asterisk means that the number of trains at the location is greater than the number of platforms.\n\n";
20252  TTFile3 << "Location,Number of,Number of,Time,Services at the location at that time\n";
20253  TTFile3 << ",Platforms,Trains,\n\n";
20254  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
20255  Ptr1 = LocServiceTimesVector.begin();
20256  Ptr2 = Ptr1 + 1;
20257  while(Ptr2 != LocServiceTimesVector.end())
20258  {
20259  PreviousService = "";
20260  ServiceAndRepeatNumTotal = "";
20261  NumTrainsAtLoc = 0;
20262  NumPlats = 0;
20263  NumPlatsAtThisLocCalculated = false;
20264  FrhCount = 0;
20265 
20266  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
20267  {
20268  PreviousService = "";
20269  ServiceAndRepeatNumTotal = "";
20270  NumTrainsAtLoc = 0;
20271  NumPlats = 0;
20272  NumPlatsAtThisLocCalculated = false;
20273  FrhCount = 0;
20274  Ptr1++;
20275  Ptr2++;
20276  if(Ptr2 == LocServiceTimesVector.end())
20277  {
20278  break;
20279  }
20280  }
20281  if(Ptr2 == LocServiceTimesVector.end())
20282  {
20283  break;
20284  }
20285  while(Ptr2->Location == Ptr1->Location)
20286  {
20287  if(Ptr1->FrhMarker == "Frh") //this test is made here and each time Ptr1 increases with Ptr1 & 2 at same loc so as to catch them all
20288  {
20289  FrhCount++;
20290  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20291  }
20292  PreviousService = "";
20293  NumTrainsAtLoc = 0;
20294  ServiceAndRepeatNumTotal = "";
20295  if((Ptr1->Location == "") && (Ptr2->Location == ""))
20296  {
20297  break;
20298  }
20299  while((Ptr2->AtLocTime != Ptr1->AtLocTime) || ((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == "")))
20300  {
20301  Ptr1++;
20302  if(Ptr1->FrhMarker == "Frh")
20303  {
20304  FrhCount++;
20305  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20306  }
20307  Ptr2++;
20308  if(Ptr2 == LocServiceTimesVector.end())
20309  {
20310  break;
20311  }
20312  if(Ptr2->Location != Ptr1->Location)
20313  {
20314  break;
20315  }
20316  }
20317  if(Ptr2 == LocServiceTimesVector.end())
20318  {
20319  break;
20320  }
20321  if(Ptr2->Location != Ptr1->Location)
20322  {
20323  break;
20324  }
20325  while(Ptr2->AtLocTime == Ptr1->AtLocTime)
20326  {
20327  if((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == ""))
20328  {
20329  break;
20330  }
20331  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
20332  {
20333  NumPlats = Track->NumberOfPlatforms(2, Ptr1->Location);
20334  NumPlatsAtThisLocCalculated = true;
20335  }
20336  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
20337  {
20338  if(ServiceAndRepeatNumTotal == "")
20339  {
20340  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum;
20341  NumTrainsAtLoc = 1;
20342  }
20343  else
20344  {
20345  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum;
20346  }
20347  }
20348  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ, has to be Ptr2 to compare Ptr1 at next round when incremented
20349  if(ServiceAndRepeatNumTotal == "")
20350  {
20351  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum;
20352  NumTrainsAtLoc = 1;
20353  }
20354  else
20355  {
20356  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum;
20357  }
20358  Ptr1 = Ptr2;
20359  if(Ptr1->FrhMarker == "Frh")
20360  {
20361  FrhCount++;
20362  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20363  }
20364  Ptr2++;
20365  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (Ptr2->AtLocTime != Ptr1->AtLocTime))
20366  {
20367 //old text //only print out if no remainers (1st condition), change in remainers (2nd condition) or change in ServiceAndRepeatNumTotalOutput, and >1 train (later condition)
20368 //new text //don't print out if all remainers or if only 1 train at loc
20369  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTAtLoc(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc); //sort into alphabetical order, remove duplicates, and calculate new value for NumTrainsAtLoc
20370 //old condits if((FrhCount == 0) || (FrhCount != LastFrhCount) || (PreviousServiceAndRepeatNumTotalOutput != ServiceAndRepeatNumTotalOutput))//don't print if same output
20371 /*new condits*/ if((NumTrainsAtLoc > 1) && ((FrhCount < NumTrainsAtLoc) || (FrhCount != LastFrhCount)))
20372  {
20373  AnsiString Asterisk = "";
20374  if(NumTrainsAtLoc > NumPlats)
20375  {
20376  Asterisk = "* ";
20377  }
20378  //print out a single line for number of trains at loc with all service refs
20379  if(FrhCount == 0)
20380  {
20381  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << "," << ServiceAndRepeatNumTotalOutput << '\n';
20382  }
20383  else if(FrhCount == 1)
20384  {
20385  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (1 remains here)," << ServiceAndRepeatNumTotalOutput << '\n';
20386  }
20387  else
20388  {
20389  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (" << FrhCount << " remain here)," << ServiceAndRepeatNumTotalOutput << '\n';
20390  }
20391  LastFrhCount = FrhCount;
20392  PreviousServiceAndRepeatNumTotalOutput = ServiceAndRepeatNumTotalOutput;
20393  AtLocsPrinted = true;
20394  ServiceAndRepeatNumTotal = "";
20395  }
20396  }
20397  if(Ptr2 == LocServiceTimesVector.end())
20398  {
20399  break;
20400  }
20401  if(Ptr2->Location != Ptr1->Location)
20402  {
20403  break;
20404  }
20405  }
20406  if(Ptr2 == LocServiceTimesVector.end())
20407  {
20408  break;
20409  }
20410  }
20411  }
20412  if(!AtLocsPrinted)
20413  {
20414  TTFile3 << "Nothing to report for trains at locations";
20415  }
20416  TTFile3 << "\n\n";
20417  //end of simultaneous AtLocs
20418  }
20419  SequenceLog += "9\n";
20420 
20421 /*
20422 //start of debugging section
20423  //print out the full vector here for testing purposes
20424  TTFile3 << "Full LocServiceTimesVector\n\n";
20425  TTFile3 << "Location,AtLocTime,ArrTime,DepTime,ServiceAndRepeatNum,Description\n\n";
20426 
20427  for(TLocServiceTimesVector::iterator Ptr = LocServiceTimesVector.begin(); Ptr != LocServiceTimesVector.end(); Ptr++)
20428  {
20429  TTFile3 << Ptr->Location << "," << Ptr->AtLocTime << "," << Ptr->ArrTime << "," << Ptr->DepTime << "," << Ptr->ServiceAndRepeatNum << "," << Ptr->FrhMarker << '\n';
20430  }
20431 
20432  TTFile3 << "\n\n\n";
20433 //end of debugging
20434 */
20435 
20436 /*cdt analysis - added at v2.10.0
20437 2 pass system: 1st extract as a single service all Snt (or Snt-sh) starts, with Fns/Sns links combined (and F-nshs/Sns-sh) (though add a new
20438 changeover code [chr XXXX - 'change ref + new reference] until come to Fjo, Frh, Frh-sh, Fer (ignore exit loc as can't stop there), ignore jbos &
20439 repeats, but with fsp & rsp store all the foregoing service entries along with the split reference & add that to the relevant Sfs entry as a new
20440 service. For shuttles with feeder start with feeder & progress into shuttle, ending when finish & remain here or progressing into the finishing
20441 service.
20442 
20443 Use The TrainDataVectorCopy as that has all unique service refs.
20444 
20445 2nd run the cdt checker similar to that in SecondPassActions, but where a same name found either side of a changeover code quote both refs. Add a
20446 similar unexpected cdt check where if have different locs either side of a cdt then may be inappropriate.
20447 
20448 First create a new TrainDataVector from earlier copy as above with single services
20449 */
20450  if(DirChecked)
20451  {
20452  //direction analysis added at v2.10.0
20453  TTrainDataEntry SingleServiceEntry, PartServiceEntry, NewPartServiceEntry, TempEntry;
20454  TTrainDataVector SingleServiceVector, PartServiceVector;
20455 
20456  //find new train services (Snt or Snt-sh) & remember that entries can be in any order
20457  //NB: ALWAYS use OtherHeadCode (which is now a service reference) for any follow-on service
20458  TTFile3 << "Train direction analysis - consisting of train facing directions on creation and possible missing or questionable changes of direction:\n\n";
20459  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
20460  {
20462  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20463  {
20464  TDE.ActionVector.erase(TDE.ActionVector.end() - 1); //strip repeat entry if present
20465  }
20466  const TActionVector &AV = TDE.ActionVector;
20467  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20468  {
20469  SingleServiceEntry = TDE;
20470  TActionVector &SSAV = SingleServiceEntry.ActionVector;
20471  for(unsigned int y = 0; y < SSAV.size(); y++)
20472  {
20473  if((SSAV.at(y).Command == "Fjo") || (SSAV.at(y).Command == "Frh") || (SSAV.at(y).Command == "Fer") || (SSAV.at(y).Command == "Frh-sh"))
20474  {
20475  SingleServiceVector.push_back(SingleServiceEntry); //push the complete entry
20476  break; //finished with this one
20477  }
20478  else if((SSAV.at(y).Command == "fsp") || (SSAV.at(y).Command == "rsp"))
20479  {
20480  PartServiceEntry = TDE; //start with complete entry
20481  PartServiceEntry.ActionVector.clear(); //clear AV
20482  for(unsigned int z = 0; z <= y; z++)
20483  {
20484  PartServiceEntry.ActionVector.push_back(TDE.ActionVector.at(z)); //add back all AVs up to & inc fsp/rsp
20485  if(z == y)
20486  {
20487  PartServiceEntry.ActionVector.at(z).Command = "chr-sp"; //change split command to chr
20488  PartServiceEntry.ActionVector.at(z).OtherHeadCode = PartServiceEntry.ActionVector.at(z).LinkedTrainEntryPtr->ServiceReference;
20489  }
20490  }
20491  PartServiceVector.push_back(PartServiceEntry);
20492  if(SSAV.at(y).Command == "fsp")
20493  {
20494  SSAV.at(y).Command = "Front split - original service continues below";
20495  SSAV.at(y).OtherHeadCode = "";
20496  }
20497  if(SSAV.at(y).Command == "rsp")
20498  {
20499  SSAV.at(y).Command = "Rear split - original service continues below";
20500  SSAV.at(y).OtherHeadCode = "";
20501  }
20502  //don't break & continue here because the original train carries on
20503  }
20504  else if(SSAV.at(y).Command == "Fns")
20505  {
20506  SSAV.at(y).Command = "chr-Fns";
20507  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20508  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20509  break; //from y loop
20510  }
20511  else if(SSAV.at(y).Command == "Fns-sh")
20512  {
20513  SSAV.at(y).Command = "chr-Fns-sh";
20514  SSAV.at(y).OtherHeadCode = SSAV.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20515  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20516  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20517  break; //from y loop
20518  }
20519  else if(SSAV.at(y).Command == "F-nshs")
20520  {
20521  SSAV.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20522  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20523  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20524  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20525  break; //from y loop
20526  }
20527  }
20528  }
20529  }
20530  SequenceLog += "10\n";
20531  //now have all complete entries in SingleServiceVector and all part services in PartServiceVector but without any follow-ons
20532 
20533  //Now add Sns & Sns-sh services to PartServiceVector entries
20534  AnsiString NextRef;
20535  while(!PartServiceVector.empty())
20536  {
20537  PartServiceEntry = PartServiceVector.at(0); //deal with front entry and add new entries at the back
20538  for(unsigned int y = 0; y < PartServiceEntry.ActionVector.size(); y++)
20539  {
20540  if(PartServiceEntry.ActionVector.at(y).Command.SubString(1,3) == "chr")
20541  {
20542  NextRef = PartServiceEntry.ActionVector.at(y).OtherHeadCode;
20543  }
20544  }
20545  //find it in TrainDataVectorCopy
20546  bool FinishType = true, FoundFlag = false;
20547  while(FinishType)
20548  {
20549  TempEntry = GetServiceFromVector(0, NextRef, TrainDataVectorCopy, FinishType, FoundFlag); //FinishType is a bool where false = Final (Fjo, Frh, Fer, or
20550  //Frh-sh); true = MoreToCome (Fns, Fns-sh, F-nshs)
20551  if(FoundFlag)
20552  {
20553  for(unsigned int y = 1; y < TempEntry.ActionVector.size(); y++) //starts at 1 as that is the entry after the start entry
20554  {
20555  if((TempEntry.ActionVector.at(y).Command == "") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20556  {
20557  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20558  }
20559  else if((TempEntry.ActionVector.at(y).Command[1] != 'F') && (TempEntry.ActionVector.at(y).Command != "fsp") && (TempEntry.ActionVector.at(y).Command != "rsp") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20560  {
20561  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20562  }
20563  else
20564  {
20565  if((TempEntry.ActionVector.at(y).Command == "Fjo") || (TempEntry.ActionVector.at(y).Command == "Frh") || (TempEntry.ActionVector.at(y).Command == "Fer") || (TempEntry.ActionVector.at(y).Command == "Frh-sh"))
20566  {
20567  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20568  SingleServiceVector.push_back(PartServiceVector.at(0)); //push the complete entry
20569  PartServiceVector.erase(PartServiceVector.begin());
20570  break; //from y loop
20571  }
20572  else if((TempEntry.ActionVector.at(y).Command == "fsp") || (TempEntry.ActionVector.at(y).Command == "rsp"))
20573  {
20574  NewPartServiceEntry = PartServiceVector.at(0); //covers everything up to but excluding the split
20575  NewPartServiceEntry.ActionVector.push_back(TempEntry.ActionVector.at(y)); //now includes the split
20576  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).Command = "chr-sp"; //change split command to chr
20577  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).OtherHeadCode = NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).LinkedTrainEntryPtr->ServiceReference;
20578  PartServiceVector.push_back(NewPartServiceEntry); //new entry for the split service
20579  if(TempEntry.ActionVector.at(y).Command == "fsp")
20580  {
20581  TempEntry.ActionVector.at(y).Command = "Front split - original service continues below";
20582  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20583  }
20584  if(TempEntry.ActionVector.at(y).Command == "rsp")
20585  {
20586  TempEntry.ActionVector.at(y).Command = "Rear split - original service continues below";
20587  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20588  }
20589  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20590  }
20591  else if(TempEntry.ActionVector.at(y).Command == "Fns")
20592  {
20593  TempEntry.ActionVector.at(y).Command = "chr-Fns";
20594  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20595  TempEntry.ActionVector.at(y).OtherHeadCode = NextRef;
20596  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20597  break; //from y loop
20598  }
20599  else if(TempEntry.ActionVector.at(y).Command == "Fns-sh")
20600  {
20601  TempEntry.ActionVector.at(y).Command = "chr-Fns-sh";
20602  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20603  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20604  NextRef = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20605  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20606  break; //from y loop
20607  }
20608  else if(TempEntry.ActionVector.at(y).Command == "F-nshs")
20609  {
20610  TempEntry.ActionVector.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20611  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20612  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20613  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20614  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20615  break; //from y loop
20616  }
20617  }
20618  }
20619  }
20620  else
20621  {
20622  SequenceLog += + "11\n";
20623  throw Exception("Unable to find service reference " + NextRef + " Last ref checked = " + TempEntry.ServiceReference);
20624  }
20625  }
20626  }
20627  if(!PartServiceVector.empty())
20628  {
20629  SequenceLog += "12\n";
20630  throw Exception("PartServiceVector should be empty here - size = " + AnsiString(PartServiceVector.size()));
20631  }
20632  SequenceLog += "13\n";
20633  /*
20634  form:-
20635  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
20636  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
20637  then multiple entries, separated by commas, of the form:-
20638 
20639  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
20640  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
20641  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
20642 
20643  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
20644  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
20645  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
20646 
20647  HH:MM;Command (cdt) }TimeCmd }
20648  HH:MM;Command;new description (dsc) }TimeCmdDescription }
20649  HH:MM;Command;new maximum speed (cms) }TimeCmdMaxSpeed }
20650  HH:MM;Location (arr & dep) }TimeLoc }
20651  HH:MM;HH:MM;Location }TimeTimeLoc }
20652  HH:MM;pas;Location }PassTime }
20653  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
20654  HH:MM;Fer;set of allowable IDs }ExitRailway }
20655  Command (Frh only) }FinRemHere }
20656 
20657  R;mm;dd;nn. Repeat Repeat entry
20658 
20659  Formats:
20660 
20661  Command only: Frh
20662  Time;Command: cdt
20663  Time;Command;new description: dsc
20664  Time;Command;new max speed: cms
20665  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
20666  Time;Command;2 Element IDs: Snt
20667  Time;Comand;n Element IDs: Fer
20668  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
20669  Time;Command;2 Element IDs;Headcode Snt-sh
20670  Time;Command;Location pas
20671  Time;Location Arr Dep
20672  Time;Time;Location Arr & dep together
20673  */
20674 
20675 /*
20676 Perform the starting direction check (Snt & Snt-sh entries). Starts with the train's front element,
20677 checking forwards until it comes to a continuation (no report), a location name that is not null and
20678 different to the train's front element name (whether null or not) (no report), a leading point
20679 (no report) or buffers (report).
20680 */
20681  bool BufferFacingUnReportedFlag = true;
20682  bool TrainFacingBuffersReported = false; //new flag added at v2.19.0 for 'no facing buffers' message instead of BufferFacingUnReportedFlag
20683  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20684  {
20685  TTrackElement ThisElement, NextElement;
20686  TTrainDataEntry TDE = SingleServiceVector.at(x);
20687  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20688  {
20689  SequenceLog += "13a\n";
20690  throw Exception("Repeat entry present in SingleServiceVector at position " + AnsiString(x));
20691  }
20692  const TActionVector &AV = TDE.ActionVector;
20693  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20694  {
20695  bool BufferFlag = false;
20696  int FrontTVPos = AV.at(0).FrontStartOrRepeatDigits;
20697  int RearTVPos = AV.at(0).RearStartOrRepeatMins;
20698  AnsiString FrontLocName = AV.at(0).LocationName;
20699  int NextEntryPos, NextExitPos;
20700  ThisElement = Track->TrackElementAt(1395, FrontTVPos);
20701  int ThisExitPos;
20702  if(ThisElement.Conn[0] == RearTVPos)
20703  {
20704  ThisExitPos = 1;
20705  }
20706  else if(ThisElement.Conn[1] == RearTVPos)
20707  {
20708  ThisExitPos = 0;
20709  }
20710  else if(ThisElement.Conn[2] == RearTVPos)
20711  {
20712  ThisExitPos = 3;
20713  }
20714  else if(ThisElement.Conn[3] == RearTVPos)
20715  {
20716  ThisExitPos = 2;
20717  }
20718  if((ThisElement.TrackType == Buffers) && (ThisExitPos == 0))//pos 0 is the buffer
20719  {
20720  BufferFlag = true;
20721  }
20722  else //continue tracking forwards
20723  {
20724  while(true)
20725  {
20726  if(ThisElement.Conn[ThisExitPos] == -1)
20727  {
20728  SequenceLog = "13b\n";
20729  throw Exception("ThisElement connects to -1 for " + TDE.ServiceReference);
20730  }
20731  NextElement = Track->TrackElementAt(1396, ThisElement.Conn[ThisExitPos]);
20732  NextEntryPos = ThisElement.ConnLinkPos[ThisExitPos];
20733  if((NextElement.TrackType == Points) && ((NextEntryPos == 0) || (NextEntryPos == 2))) //leading points
20734  {
20735  BufferFlag = false; //should already be false
20736  break;
20737  }
20738  else if(NextElement.TrackType == Continuation)
20739  {
20740  BufferFlag = false;
20741  break;
20742  }
20743  else if((NextElement.ActiveTrackElementName != "") && (NextElement.ActiveTrackElementName != FrontLocName))
20744  {
20745  BufferFlag = false;
20746  break;
20747  }
20748  else if(NextElement.TrackType == Buffers)
20749  {
20750  BufferFlag = true;
20751  break;
20752  }
20753  else if((NextElement.TrackType == Points) && ((NextEntryPos == 1) || (NextEntryPos == 3))) //trailing points
20754  {
20755  ThisElement = NextElement;
20756  ThisExitPos = 0;
20757  continue;
20758  }
20759  else
20760  {
20761  if(NextEntryPos == 0)
20762  {
20763  NextExitPos = 1;
20764  }
20765  else if(NextEntryPos == 1)
20766  {
20767  NextExitPos = 0;
20768  }
20769  else if(NextEntryPos == 2)
20770  {
20771  NextExitPos = 3;
20772  }
20773  else if(NextEntryPos == 3)
20774  {
20775  NextExitPos = 2;
20776  }
20777  }
20778  ThisElement = NextElement;
20779  ThisExitPos = NextExitPos;
20780  }
20781  }
20782  if(BufferFlag)
20783  {
20784  if(BufferFacingUnReportedFlag)
20785  {
20786  TTFile3 << "Train facing direction on creation analysis:-\n\n";
20787  BufferFacingUnReportedFlag = false;
20788  }
20789  if(AV.at(1).Command != "cdt") //added at v2.19.0
20790  {
20791  TTFile3 << "Service " + TDE.ServiceReference + " facing buffers on creation with no immediate change of direction\n";
20792  TrainFacingBuffersReported = true; //added at v2.19.0
20793  }
20794  }
20795  }
20796  }
20797  if(!TrainFacingBuffersReported) //added at v2.19.0
20798  {
20799  TTFile3 << "Nothing to report for train facing directions\n\n";
20800  }
20801  else
20802  {
20803  TTFile3 << '\n';
20804  }
20805  SequenceLog += "13c\n";
20806 
20807  //Perform the missing cdt check. Check every entry similar to the check in SecondPassActions and if find any print out the full sequence and service entries
20808  AnsiString LocationNameToBeChecked = "";
20809  bool MissingcdtUnreportedFlag = true;
20810  TNumList MarkerList;
20811  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20812  {
20813  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20814  unsigned int y = 0;
20815  int FirstInstance = 9999, SecondInstance = 9999; //9999 ensures won't be marked if not changed
20816  bool FullBreak = false;
20817  MarkerList.clear();
20818  // first discard unlocated Snt entries as they don't have location name set
20819  while((y < TDEntry.ActionVector.size()) && !FullBreak)
20820  // need to check each location name separately in turn, skipped for SignallerControl entries
20821  {
20822  if(/*(TDEntry.ActionVector.at(y).Command == "Fer") || */(TDEntry.ActionVector.at(y).FormatType == Repeat) ||
20823  (TDEntry.ActionVector.at(y).Command == "Fjo") || (TDEntry.ActionVector.at(y).Command == "Frh") ||
20824  (TDEntry.ActionVector.at(y).Command == "Frh-sh"))
20825  {
20826  break; // out of the 'while' loop since have reached the end
20827  }
20828  LocationNameToBeChecked = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20829  if((TDEntry.ActionVector.at(y).Command == "Snt") && (TDEntry.ActionVector.at(y).LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20830  {
20831  int EntryPos = TDEntry.ActionVector.at(0).RearStartOrRepeatMins; //this is a track vector position
20832  LocationNameToBeChecked = Track->TrackElementAt(1678, EntryPos).ActiveTrackElementName;
20833  }
20834  if(LocationNameToBeChecked == "")
20835  {
20836  if(y == 0) //unlocated and un-named Snt, so skip this value of y
20837  {
20838  y++;
20839  continue;
20840  }
20841  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName; //the only un-named values for ActionVectorEntry::LocationName
20842  } //are for unlocated Snts and Fers
20843  FirstInstance = y;
20844  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
20845  {
20846  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
20847  if(/*(AVEntry.Command == "Fer") || */(AVEntry.FormatType == Repeat) ||
20848  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20849  (AVEntry.Command == "Frh-sh"))
20850  {
20851  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
20852  }
20853  if(AVEntry.Command == "cdt")
20854  {
20855  break; // out of the 'z' loop since the check is only valid up to a change of direction
20856  }
20857  if(AVEntry.LocationName == LocationNameToBeChecked)
20858  {
20859  continue; // keep going while name same
20860  }
20861  if(AVEntry.LocationName != LocationNameToBeChecked)
20862  // if name different check forwards to see if repeats
20863  {
20864  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
20865  {
20866  AnsiString LocationName;
20867  if(TDEntry.ActionVector.at(a).Command == "cdt")
20868  {
20869  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
20870  }
20871  if(TDEntry.ActionVector.at(a).Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20872  {
20873  int ExitLoc = TDEntry.ActionVector.at(a).ExitList.front();
20874  LocationName = Track->TrackElementAt(1679, ExitLoc).ActiveTrackElementName;
20875  }
20876  else
20877  {
20878  LocationName = TDEntry.ActionVector.at(a).LocationName;
20879  }
20880  if(LocationName == LocationNameToBeChecked)
20881  {
20882  SecondInstance = a;
20883  AnsiString Sequence = TDEntry.ServiceReference;
20884  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20885  {
20886  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20887  {
20888  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20889  }
20890  }
20891  if(MissingcdtUnreportedFlag)
20892  {
20893  TTFile3 << "Possibly missing changes of direction - these should be checked to see if there is a good reason for cdt's not being included. :-\n\n";
20894  }
20895  TTFile3 << LocationNameToBeChecked << " is listed twice with no direction change between in service sequence: " << Sequence << "\n\n";
20896  MarkerList.push_back(FirstInstance);
20897  MarkerList.push_back(SecondInstance);
20898  SingleServiceOutput(0, x, MarkerList, SingleServiceVector, TTFile3);
20899  MissingcdtUnreportedFlag = false;
20900  FullBreak = true; //no more checks for this sequence
20901  break; //out of the a & z loops
20902  }
20903  }
20904  break; // out of the 'z' loop since have checked 'a' as far as need to
20905  }
20906  }
20907  y++;
20908  }
20909  }
20910  if(MissingcdtUnreportedFlag)
20911  {
20912  TTFile3 << "Nothing to report for missing changes of direction\n\n";
20913  }
20914  else
20915  {
20916  TTFile3 << '\n';
20917  }
20918  SequenceLog += "14\n";
20919 
20920 /* Perform the questionable cdt check. Examine each service in the SingleServiceVector, and if don't find the same
20921  name either side of a cdt (before the next cdt) then flag as a questionable cdt.
20922  Method: Have an outer loop for each service that looks for cdts. When found work backwards to the last cdt or beginning and std::list all the
20923  locations excluding the cdt location. Then work forwards to the next cdt or the end and do the same. Sort each list and make unique (duplicated
20924  names on one side of a cdt already checked either by the tt validator or the missing cdt check). Then compare the two lists and if any location
20925  included in both then ok, else report as questionable. If one list is empty then it is reported.
20926 */
20927  typedef std::list<AnsiString> TLocList;
20928  TLocList BackwardList, ForwardList;
20929  bool IntroLineNeeded = true;
20930  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20931  {
20932  unsigned int cdtPosition = 9999;
20933  AnsiString cdtLocation = "";
20934  bool FoundSameName = false;
20935  bool FerEntry = false;
20936  MarkerList.clear();
20937  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20938  for(unsigned int y = 0; y <= TDEntry.ActionVector.size(); y++) // <= because need to examine Fer endings after reached end of vector
20939  // need to check each location name separately in turn, skipped for SignallerControl entries
20940  {
20941  if((y == TDEntry.ActionVector.size()) && !FerEntry)
20942  {
20943  break;
20944  }
20945  BackwardList.clear();
20946  ForwardList.clear();
20947  bool ValidEnd = false;
20948  if(y < TDEntry.ActionVector.size())
20949  {
20950  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20951  if((AVEntry.FormatType == Repeat) || //end of SSVector, shouldn't be any repeats
20952  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20953  (AVEntry.Command == "Frh-sh"))
20954  {
20955  ValidEnd = true;
20956  }
20957  }
20958  if(FerEntry || ValidEnd)
20959  {
20960  if(MarkerList.empty())
20961  {
20962  break; // out of the 'y' loop since have reached the end & nothing to report
20963  }
20964  else
20965  {
20966  AnsiString Sequence = TDEntry.ServiceReference;
20967  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20968  {
20969  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20970  {
20971  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20972  }
20973  }
20974  MarkerList.sort();
20975  if(IntroLineNeeded)
20976  {
20977  TTFile3 << "Questionable change of direction analysis.\n\n";
20978  TTFile3 << "For marked changes of direction there are no same-name locations listed both above (up to the start or another direction change)\n";
20979  TTFile3 << "and below (down to the end or another direction change) but not counting the change of direction location itself.\n\n";
20980  TTFile3 << "These changes of direction are probably valid for movements to and from depots but all should be checked to\n";
20981  TTFile3 << "make sure that none has been included incorrectly:\n\n";
20982  IntroLineNeeded = false;
20983  }
20984  TTFile3 << "Service sequence " << Sequence << " contains questionable changes of direction:-\n\n";
20985  SingleServiceOutput(1, x, MarkerList, SingleServiceVector, TTFile3);
20986  break;
20987  }
20988  }
20989  if(y < TDEntry.ActionVector.size()) //if it is == ...size() then shouldn't have reached here
20990  {
20991  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20992  if(AVEntry.Command != "cdt")
20993  {
20994  continue; //only looking for cdts
20995  }
20996  //here have found a cdt
20997  cdtPosition = y;
20998  cdtLocation = AVEntry.LocationName;
20999  for(int z = y - 1; z >= 0; z--)
21000  {
21001  const TActionVectorEntry &AVEntry2 = TDEntry.ActionVector.at(z);
21002  if(AVEntry2.Command == "cdt")
21003  {
21004  break; //don't look further back than the last cdt
21005  }
21006  AnsiString LocName = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
21007  if((AVEntry2.Command == "Snt") && (AVEntry2.LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
21008  {
21009  int EntryPos = AVEntry2.RearStartOrRepeatMins; //this is a track vector position
21011  }
21012  if(LocName == "")
21013  {
21014  LocName = AVEntry2.LocationName; //the only un-named values for ActionVectorEntry::LocationName
21015  } //are for unlocated Snts and Fers
21016  if((LocName != "") && (AVEntry2.LocationName != cdtLocation)) //if an earlier entry == cdtLocation will have been picked up in an earlier check
21017  {
21018  BackwardList.push_back(LocName);
21019  }
21020  }
21021  BackwardList.sort();
21022  BackwardList.unique();
21023  for(unsigned int z = y + 1; z < TDEntry.ActionVector.size(); z++)
21024  {
21025  const TActionVectorEntry &AVEntry3 = TDEntry.ActionVector.at(z);
21026  if(/*(AVEntry3.Command == "Fer") || */(AVEntry3.FormatType == Repeat) ||
21027  (AVEntry3.Command == "Fjo") || (AVEntry3.Command == "Frh") ||
21028  (AVEntry3.Command == "Frh-sh") || (AVEntry3.Command == "cdt"))
21029  {
21030  break; // out of the 'z' loop since have reached another cdt or the end
21031  }
21032  AnsiString LocName = "";
21033  if(AVEntry3.Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
21034  {
21035  int ExitLoc = AVEntry3.ExitList.front();
21036  LocName = Track->TrackElementAt(1681, ExitLoc).ActiveTrackElementName;
21037  FerEntry = true;
21038  }
21039  else
21040  {
21041  LocName = AVEntry3.LocationName;
21042  }
21043  if((LocName != "") && (AVEntry3.LocationName != cdtLocation)) //if a later entry == cdtLocation will have been picked up in an earlier check
21044  {
21045  ForwardList.push_back(LocName);
21046  }
21047  }
21048  ForwardList.sort();
21049  ForwardList.unique();
21050  FoundSameName = false;
21051  //now have both lists compiled (may be empty) so check for same name in both & report if don't find any
21052  if(!BackwardList.empty() && !ForwardList.empty())
21053  {
21054  for(TLocList::iterator BLIt = BackwardList.begin(); BLIt != BackwardList.end(); BLIt++)
21055  {
21056  for(TLocList::iterator FLIt = ForwardList.begin(); FLIt != ForwardList.end(); FLIt++)
21057  {
21058  if(*BLIt == *FLIt)
21059  {
21060  FoundSameName = true;
21061  }
21062  }
21063  }
21064  }
21065  if(!FoundSameName) //report the inability to find same name
21066  {
21067  MarkerList.push_back(cdtPosition);
21068  }
21069  }
21070  }
21071  }
21072  if(IntroLineNeeded)
21073  {
21074  TTFile3 << "Nothing to report for questionable changes of direction\n\n";
21075  }
21076  else
21077  {
21078  TTFile3 << '\n';
21079  }
21080 /*
21081 //debug section
21082 //print all SSVector for diagnostic purposes
21083  TTFile3 << "Whole SSVector\n\n";
21084  TNumList EmptyList;
21085  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
21086  {
21087  SingleServiceOutput(, x, EmptyList, SingleServiceVector, TTFile3);
21088  }
21089 //end of debug section
21090 */
21091  }
21092  SequenceLog += "15\n";
21093  TTFile3.close();
21094  Utilities->CallLogPop(2212);
21095  return(true);
21096  }
21097 
21098  catch(const Exception &e) //non error catch
21099  {
21100  AnsiString TTErrorFileName = "Analysis Error.txt";
21101  TTErrorFileName = CurDir + "\\Formatted timetables\\" + TTErrorFileName;
21102  std::ofstream TTError(TTErrorFileName.c_str());
21103  if(TTError == 0)
21104  {
21105  ShowMessage("Analysis error file failed to open - can't be created");
21106  Utilities->CallLogPop(2233);
21107  return(false);
21108  }
21109  AnsiString TimeNow = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
21110  TTError << TimeNow.c_str() << '\n' << ArrRange << '\n' << ArrChecked << '\n' << DepRange << '\n' <<
21111  DepChecked << '\n' << AtLocChecked << '\n' << SequenceLog << '\n' << AnsiString(e.Message);
21112 
21113  TTError.close();
21114  ShowMessage("Error in Conflict Analysis: A file called 'Analysis Error.txt' has been created in your Formatted timetables folder. Please send this file together with your railway and timetable files to railwayfeedback@gmail.com for investigation - many thanks");
21115  Utilities->CallLogPop(2226);
21116  return(false);
21117  }
21118 }
21119 
21120 // ---------------------------------------------------------------------------
21121 void TTrainController::SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
21122 {
21123  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(SSVectorNumber) + ',' + ",SingleServiceOutput");
21124  if((SSVectorNumber < 0) || ((unsigned int)SSVectorNumber >= SingleServiceVector.size()))
21125  {
21126  throw Exception("SSVectorNumber out of range, = " + AnsiString(SSVectorNumber) + ", Vector size = " + SingleServiceVector.size());
21127  }
21128  TTrainDataEntry SingleService = SingleServiceVector.at(SSVectorNumber);
21129  {
21130  VecFile << ",Initial service reference " << SingleService.ServiceReference + '\n';
21131  AnsiString Marker = "";
21132  for(unsigned int x = 0; x < SingleService.ActionVector.size(); x++)
21133  {
21134  Marker = ',';
21135  for(TNumListIterator MLIt = MarkerList.begin(); MLIt != MarkerList.end(); MLIt++)
21136  {
21137  if(int(x) == *MLIt)
21138  {
21139  Marker = "-->,";
21140  break;
21141  }
21142  }
21143  TActionVectorEntry AVE = SingleService.ActionVector.at(x);
21144  if(AVE.FormatType == StartNew)
21145  {
21146  AnsiString RearID = Track->TrackElementAt(1397, AVE.RearStartOrRepeatMins).ElementID;
21147  AnsiString FrontID = Track->TrackElementAt(1398, AVE.FrontStartOrRepeatDigits).ElementID;
21148  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << '\n';
21149  }
21150  if(AVE.FormatType == SNTShuttle)
21151  {
21152  AnsiString RearID = Track->TrackElementAt(1399, AVE.RearStartOrRepeatMins).ElementID;
21153  AnsiString FrontID = Track->TrackElementAt(1400, AVE.FrontStartOrRepeatDigits).ElementID;
21154  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << ' ' << AVE.OtherHeadCode << '\n';
21155  }
21156  if(AVE.FormatType == SNSShuttle) //should all have been converted to chr
21157  {
21158  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << ' ' << AVE.NonRepeatingShuttleLinkHeadCode << '\n';
21159  }
21160  if(AVE.FormatType == Repeat) //shouldn't be any repeats, only here to show if any have been copied
21161  {
21162  VecFile << Marker << "Repeat " << AVE.RearStartOrRepeatMins << ' ' << AVE.FrontStartOrRepeatDigits << ' ' << AVE.NumberOfRepeats << '\n';
21163  }
21164  if((AVE.FormatType == TimeCmd) || (AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle) || (AVE.FormatType == FSHNewService))
21165  {
21166  TActionVectorEntry AVHolder = AVE;
21167  if(AVE.Command.SubString(1,3) == "chr")
21168  {
21169  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "sp")
21170  {
21171  AVE.Command = "Change of service to " + AVE.OtherHeadCode + " after split";
21172  AVE.OtherHeadCode = "";
21173  }
21174  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns")
21175  {
21176  AVE.Command = "Change of service to ";
21177  }
21178  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns-sh")
21179  {
21180  AVE.Command = "Change to shuttle finishing service";
21181  }
21182  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "F-nshs")
21183  {
21184  AVE.Command = "Change to shuttle service " + AVE.OtherHeadCode + " from feeder";
21185  AVE.OtherHeadCode = "";
21186  }
21187  }
21188  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << '\n';
21189  AVE = AVHolder;
21190  }
21191  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
21192  {
21193  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
21194  }
21195  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
21196  {
21197  VecFile << Marker << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
21198  }
21199  else if(AVE.FormatType == TimeTimeLoc)
21200  {
21201  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
21202  }
21203  else if(AVE.FormatType == PassTime)
21204  {
21205  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
21206  }
21207  else if(AVE.FormatType == ExitRailway) //ListOfExits added at v2.10.0
21208  {
21209  AnsiString ListOfExits = "";
21210  for(TNumListIterator NLIt = AVE.ExitList.begin(); NLIt != AVE.ExitList.end(); NLIt++)
21211  {
21212  ListOfExits += AnsiString(Track->TrackElementAt(1432, *NLIt).ElementID) + ' ';
21213  }
21214  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << " Fer " << ListOfExits <<'\n';
21215  }
21216  else if(AVE.FormatType == FinRemHere)
21217  {
21218  VecFile << Marker << "Frh" << '\n';
21219  }
21220  }
21221  VecFile << '\n';
21222  }
21223  Utilities->CallLogPop(2318);
21224 }
21225 
21226 // ---------------------------------------------------------------------------
21227 
21228 TTrainDataEntry TTrainController::GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
21229 {
21230  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetServiceFromVector," + HeadCode);
21231  FoundFlag = false;
21232  FinishType = true;
21233  for(unsigned int x = 0; x < Vector.size(); x++)
21234  {
21235 // AnsiString ThisRef = Vector.at(x).ServiceReference;
21236  if(Vector.at(x).ServiceReference == HeadCode)
21237  {
21238  if(Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1).FormatType == Repeat) //shouldn't be any repeats
21239  {
21240  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 2);
21241  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
21242  {
21243  FinishType = false;
21244  }
21245  }
21246  else
21247  {
21248  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1);
21249  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
21250  {
21251  FinishType = false;
21252  }
21253  }
21254  FoundFlag = true;
21255  Utilities->CallLogPop(2319);
21256  return(Vector.at(x));
21257  }
21258  }
21259  Utilities->CallLogPop(2320);
21260  return(Vector.at(Vector.size() - 1)); //return last for want of returning something
21261 }
21262 
21263 // ---------------------------------------------------------------------------
21264 
21265 bool TTrainController::WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange) //times are "HH:MM"
21266 {
21267 //convert times to integer minutes
21268  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WithinTimeRange," + Time1 + "," + Time2 + "," + AnsiString(MinuteRange));
21269  if((Time1 == "") || (Time2 == ""))
21270  {
21271  Utilities->CallLogPop(2213);
21272  return(false);
21273  }
21274  int Mins = Time1.SubString(4,2).ToInt();
21275  int Hours = Time1.SubString(1,2).ToInt();
21276  int Time1Mins = (Hours * 60) + Mins;
21277  Mins = Time2.SubString(4,2).ToInt();
21278  Hours = Time2.SubString(1,2).ToInt();
21279  int Time2Mins = (Hours * 60) + Mins;
21280  if(abs(Time1Mins - Time2Mins) <= MinuteRange)
21281  {
21282  Utilities->CallLogPop(2214);
21283  return(true);
21284  }
21285  Utilities->CallLogPop(2215);
21286  return(false);
21287 }
21288 
21289 // ---------------------------------------------------------------------------
21290 
21291 AnsiString TTrainController::ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival,
21292  bool &AnalysisError, int &MaxNumberOfSameDirections)
21293 {
21294  //input consists of services and service Arr or Dep times as a comma separated list, Location needed to determine direction information
21295 
21296  try
21297  {
21298  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTArrDep," + Input);
21299  AnsiString Output = "", OneService = "", TempStr1 = "", TempStr2 = "";
21300  int SCPos = 0;
21301  std::list<AnsiString> ServiceList; //this is the list of services with times extracted from Input - not to be confused with ServiceCallingPointsList
21302  //first change every second comma in Input to a semicolon so can separate services but keep times with services
21303  bool EvenComma = false;
21304  for(int x = 1; x <= Input.Length(); x++)
21305  {
21306  TempStr1 = Input[x];
21307  if(TempStr1 == AnsiString(',') && EvenComma)
21308  {
21309  TempStr2 += ';';
21310  }
21311  else
21312  {
21313  TempStr2 += Input[x];
21314  }
21315  if(TempStr1 == AnsiString(','))
21316  {
21317  EvenComma = !EvenComma;
21318  }
21319  }
21320  //load up the list of services with associated times
21321  while(TempStr2.Length() > 0)
21322  {
21323  SCPos = TempStr2.Pos(';');
21324  if(SCPos > 0) //0 if not found, as won't be when only one service left
21325  {
21326  OneService = TempStr2.SubString(1, SCPos - 1);
21327  ServiceList.push_back(OneService);
21328  TempStr2 = TempStr2.SubString(SCPos + 1, TempStr2.Length() - SCPos);
21329  }
21330  else //no semicolon so looking at last (or only) element
21331  {
21332  ServiceList.push_back(TempStr2);
21333  TempStr2 = "";
21334  }
21335  }
21336  ServiceList.sort(); // alphabetical order
21337  ServiceList.unique(); //remove duplicates
21338  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
21339 
21340  //now add direction information from AllServiceCallingLocsMap - key is service ref and value a list of calling points in order
21341  int DirectionMarker = 0; //this is added in & runs from 1 upwards, same marker for diff services = same direction
21342  //first add the direction marker "&0" for not yet allocated - '&' is an identifier
21343  std::list<AnsiString>::iterator SLIt, SLIt1, SLIt2, SLIt3;
21344 
21345  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21346  {
21347  *SLIt = *SLIt + "&0"; //add in a basic direction marker to each service
21348  }
21349  SLIt3 = ServiceList.end();
21350  SLIt3--; //so points to last element
21351  AnsiString ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatInfo1, RepeatInfo2; //1 refers to first for..next loop & 2 to second
21352  int AmpersandPos, SpacePos, CommaPos1, CommaPos2, RepeatNum1, RepeatNum2;
21353  TAllServiceCallingLocsMap::iterator ASCLIt1, ASCLIt2;
21354  TServiceCallingLocsList ServiceCallingLocsList1, ServiceCallingLocsList2;
21355  MaxNumberOfSameDirections = 0; //at end of each SLIt loop if SameDirectionCount > MaxNumberOfSameDirections then MaxNumberOfSameDirections = SameDirectionCount
21356  int SameDirectionCount = 0; //starts at 1 at each SLIt loop (because SLIt1 entry already has a DirectionMarker) and increments for every same direction
21357 
21358  for(std::list<AnsiString>::iterator SLIt1 = ServiceList.begin(); SLIt1 != SLIt3; SLIt1++) //should be end() - 1 but can't use -1 with lists so have to improvise
21359  {
21360  SLIt = SLIt1;
21361  SLIt++; //so points to one after SLIt1
21362  if(SLIt1->SubString(SLIt1->Length() - 1, 2) != AnsiString("&0"))
21363  {
21364  continue; //already allocated so skip to the next
21365  }
21366  else
21367  {
21368  CommaPos1 = SLIt1->Pos(','); //can't be 0
21369  ServiceRef1 = SLIt1->SubString(1, CommaPos1 - 1);
21370  //but this contains "(First service..." etc so need to strip these, but use to extract RepeatNum
21371  SpacePos = ServiceRef1.Pos(' ');
21372  RepeatNum1 = 0;
21373  if(SpacePos > 0) //otherwise it's already correct
21374  {
21375  RepeatInfo1 = ServiceRef1.SubString(SpacePos + 2, ServiceRef1.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
21376  ServiceRef1 = ServiceRef1.SubString(1, SpacePos - 1);
21377  if(RepeatInfo1[1] == 'F')
21378  {
21379  RepeatNum1 = 0;
21380  }
21381  else
21382  {
21383  SpacePos = RepeatInfo1.Pos(' ');
21384  RepeatNum1 = RepeatInfo1.SubString(SpacePos + 1, RepeatInfo1.Length() - SpacePos).ToInt();
21385  }
21386  }
21387  AnsiTime1 = SLIt1->SubString(CommaPos1 + 1, SLIt1->Length() - CommaPos1);
21388  //but this includes the "&0" etc so need to strip these
21389  AmpersandPos = AnsiTime1.Pos('&');
21390  AnsiTime1 = AnsiTime1.SubString(1, AmpersandPos - 1);
21391 
21392  ASCLIt1 = AllServiceCallingLocsMap.find(ServiceRef1);
21393  if(ASCLIt1 == AllServiceCallingLocsMap.end()) //can't find it
21394  {
21395  throw Exception("ASCLIt1 Error in " + Input);
21396  }
21397  ServiceCallingLocsList1 = ASCLIt1->second;
21398  AmpersandPos = SLIt1->Pos('&');
21399  *SLIt1 = SLIt1->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21400  *SLIt1 = *SLIt1 + AnsiString(++DirectionMarker); //now add the next marker (pre-increment), allow for it being more than one digit
21401 
21402  SameDirectionCount = 1;
21403  for(SLIt2 = SLIt; SLIt2 != ServiceList.end(); SLIt2++)
21404  {
21405  CommaPos2 = SLIt2->Pos(','); //can't be 0
21406  ServiceRef2 = SLIt2->SubString(1, CommaPos2 - 1);
21407  //but this contains "(First service..." etc so need to strip these
21408  SpacePos = ServiceRef2.Pos(' ');
21409  RepeatNum2 = 0;
21410  if(SpacePos > 0) //otherwise it's already correct
21411  {
21412  RepeatInfo2 = ServiceRef2.SubString(SpacePos + 2, ServiceRef2.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
21413  ServiceRef2 = ServiceRef2.SubString(1, SpacePos - 1);
21414  if(RepeatInfo2[1] == 'F')
21415  {
21416  RepeatNum2 = 0;
21417  }
21418  else
21419  {
21420  SpacePos = RepeatInfo2.Pos(' ');
21421  RepeatNum2 = RepeatInfo2.SubString(SpacePos + 1, RepeatInfo2.Length() - SpacePos).ToInt();
21422  }
21423  }
21424  AnsiTime2 = SLIt2->SubString(CommaPos2 + 1, SLIt2->Length() - CommaPos2);
21425  //but this includes the "&0" etc so need to strip these
21426  AmpersandPos = AnsiTime2.Pos('&');
21427  AnsiTime2 = AnsiTime2.SubString(1, AmpersandPos - 1);
21428 
21429  ASCLIt2 = AllServiceCallingLocsMap.find(ServiceRef2);
21430  if(ASCLIt2 == AllServiceCallingLocsMap.end()) //can't find it
21431  {
21432  throw Exception("ASCLIt2 Error in " + Input);
21433  }
21434  ServiceCallingLocsList2 = ASCLIt2->second;
21435  //now compare the two
21436  if(SameDirection(0, ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatNum1, RepeatNum2, ServiceCallingLocsList1, ServiceCallingLocsList2, Location, Arrival))
21437  {
21438  int AmpersandPos = SLIt2->Pos('&');
21439  *SLIt2 = SLIt2->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21440  *SLIt2 = *SLIt2 + AnsiString(DirectionMarker); //now add the same marker as *SLIt1
21441  SameDirectionCount++;
21442  }
21443  }
21444  if(SameDirectionCount > MaxNumberOfSameDirections)
21445  {
21446  MaxNumberOfSameDirections = SameDirectionCount;
21447  }
21448  }
21449  }
21450 
21451  if(SLIt3->SubString(SLIt3->Length() - 1, 2) == AnsiString("&0")) //*SLTIt3 is the last in the list and may not have been allocated, if not it doesn't match
21452  {
21453  //any existing direction so allocate it now
21454  AmpersandPos = SLIt3->Pos('&');
21455  *SLIt3 = SLIt3->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21456  *SLIt3 = *SLIt3 + AnsiString(++DirectionMarker);
21457  }
21458  //now change direction markers to upper case letters beginning with 'A' (and continuing with 'AA' if exceed 26) & add a comma before so have ServiceRef, DirectionMarker, Time
21459  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21460  {
21461  //extract the DirectionMarker as an integer
21462  AmpersandPos = SLIt->Pos('&');
21463  AnsiString DirectionMarkerString = SLIt->SubString(AmpersandPos + 1, SLIt->Length() - AmpersandPos); //extract the number as an ansistring
21464  AnsiString ServiceWithoutMarker = SLIt->SubString(1, AmpersandPos - 1); //truncate the &number
21465  DirectionMarker = DirectionMarkerString.ToInt();
21466  AnsiString DirectionSuffix = "";
21467  char c;
21468  if(DirectionMarker < 27)
21469  {
21470  c = 64 + DirectionMarker; //so 1 -> 'A'
21471  DirectionSuffix = "," + AnsiString(c);
21472  }
21473  else if(DirectionMarker < 53)
21474  {
21475  c = 65 + DirectionMarker - 27; //so 27 -> 'AA'
21476  DirectionSuffix = ",A" + AnsiString(c);
21477  }
21478  else
21479  {
21480  DirectionSuffix = ",?"; //shouldn'tn ever get this far!
21481  }
21482  *SLIt = ServiceWithoutMarker + DirectionSuffix;
21483  }
21484  //now prepare the final consolidated output
21485  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21486  {
21487  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21488  }
21489  if(Output.Length() > 0)
21490  {
21491  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21492  }
21493  Utilities->CallLogPop(2216);
21494  return(Output);
21495  }
21496 
21497  catch(const Exception &e) //non error catch
21498  {
21499  AnalysisError = true;
21500  Utilities->CallLogPop(2227);
21501  return(e.Message);
21502  }
21503 }
21504 
21505 // ---------------------------------------------------------------------------
21506 
21507 AnsiString TTrainController::ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
21508 {
21509  //similar to above but doesn't include times in the input
21510  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTAtLoc," + Input);
21511  AnsiString InternalInput = Input, Output = "", OneService = "";
21512  int CommaPos = 0;
21513  std::list<AnsiString> ServiceList;
21514  //load up the list
21515  while(InternalInput.Length() > 0)
21516  {
21517  CommaPos = InternalInput.Pos(',');
21518  if(CommaPos > 0) //0 if not found, as won't be when only one service left
21519  {
21520  OneService = InternalInput.SubString(1, CommaPos - 1);
21521  ServiceList.push_back(OneService);
21522  InternalInput = InternalInput.SubString(CommaPos + 1, InternalInput.Length() - CommaPos);
21523  }
21524  else //no comma so looking at last (or only) element
21525  {
21526  ServiceList.push_back(InternalInput);
21527  InternalInput = "";
21528  }
21529  }
21530 
21531  ServiceList.sort(); // alphabetical order
21532  ServiceList.unique(); //remove duplicates
21533  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
21534  for(std::list<AnsiString>::iterator SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21535  {
21536  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21537  }
21538  if(Output.Length() > 0)
21539  {
21540  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21541  }
21542  Utilities->CallLogPop(2217);
21543  return(Output);
21544 }
21545 
21546 // ---------------------------------------------------------------------------
21547 
21548 
21549 bool TTrainController::SameDirection(int Caller, AnsiString Ref1In, AnsiString Ref2In, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1,
21550  TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
21551 {
21552  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SameDirection," + Ref1In + "," + Ref2In + "," + Time1 + "," + Time2 + "," +
21553  AnsiString(RepeatNum1) + "," + AnsiString(RepeatNum2) + "," + Location);
21554 
21555  std::list<AnsiString>::iterator LP1, LP2, ListPtr1, ListPtr2, LocPtr1, LocPtr2; //LP1 & 2 are temporary pointers, ListPtrs are
21556  //general list pointers, LocPtrs point to Location in the two lists
21557 
21558  //first find the relevant values for LocPtr1 & LocPtr2 taking account of cdts and times
21559  //for List1
21560  bool LocFound = false;
21561  AnsiString Ref1 = Ref1In, Ref2 = Ref2In;
21562  int IncMinutes;
21563  TDateTime FirstServiceTime;
21564 
21565  //first need to strip off /1, /2 etc if present from Ref1 & Ref2 (leave Ref1In & Ref2In for error message & retain value as target in finding the correct reference for cdts)
21566  int Ref1Target = 0, Ref1Count = 0;
21567  int Ref2Target = 0, Ref2Count = 0;
21568 
21569 /* drop this after retained slashes in ServiceRef
21570  int SlashPos = Ref1.Pos('/');
21571  if(SlashPos > 0) //if 0 Ref1 == Ref1In & target stays at 0
21572  {
21573  Ref1Target = Ref1.SubString(SlashPos + 1, Ref1.Length() - SlashPos).ToInt();
21574  Ref1 = Ref1.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21575  }
21576  int Ref2Target = 0, Ref2Count = 0;
21577  SlashPos = Ref2.Pos('/');
21578  if(SlashPos > 0) //if 0 leave as is
21579  {
21580  Ref2Target = Ref2.SubString(SlashPos + 1, Ref2.Length() - SlashPos).ToInt();
21581  Ref2 = Ref2.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21582  }
21583 */
21584 
21585  for(ListPtr1 = List1.begin(); ListPtr1 != List1.end(); ListPtr1++) //note that when this routine entered Ref1In & Ref2In are already set to the correct services,
21586  {
21587  //even if others have same names. But if there are cdt's then need to refind the correct service
21588  if((*ListPtr1) == Location) //
21589  {
21590  LocPtr1 = ListPtr1; //may be modified later
21591  LocFound = true;
21592  }
21593  if(ListPtr1->SubString(1, 3) == "%%%")
21594  {
21595  AnsiString CDTTime = ListPtr1->SubString(4, 5);
21596  //now adjust the time to correspond to the repeat if there is one
21597  if(RepeatNum1 > 0) //if it is 0 then AnsiTime1 is already valid
21598  {
21599  IncMinutes = -1;
21600  FirstServiceTime = TDateTime(-1);
21601  bool BreakFlag = false;
21602  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21603  {
21604  if(TDVIt->ServiceReference == Ref1)
21605  {
21606  if(Ref1Target > Ref1Count)
21607  {
21608  Ref1Count++;
21609  continue;
21610  }
21611  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21612  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21613  {
21614  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21615  {
21616  FirstServiceTime = AVIt->EventTime; //i.e. the FirstService value of CDTTime
21617  BreakFlag = true;
21618  break;
21619  }
21620  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime) //add arr & dep in case find sooner (though dep shouldn't be sooner)
21621  {
21622  FirstServiceTime = AVIt->ArrivalTime;
21623  BreakFlag = true;
21624  break;
21625  }
21626  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21627  {
21628  FirstServiceTime = AVIt->DepartureTime;
21629  BreakFlag = true;
21630  break;
21631  }
21632  }
21633  if(BreakFlag)
21634  {
21635  break;
21636  }
21637  }
21638  }
21639  if(IncMinutes == -1)
21640  {
21641  throw Exception("Failed to find service for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21642  }
21643  if(FirstServiceTime == TDateTime(-1))
21644  {
21645  throw Exception("Failed to find first service time for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21646  }
21647  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(60, FirstServiceTime, RepeatNum1, IncMinutes));
21648  }
21649  if(!Arrival && (Time1 == CDTTime)) //continue if equal in case next is a departure for the Location
21650  {
21651  LocFound = false;
21652  continue;
21653  }
21654  if(Arrival && (Time1 == CDTTime)) //gone far enough so can stop
21655  {
21656  break;
21657  }
21658  if(Time1 > CDTTime) //not there yet so go on
21659  {
21660  LocFound = false;
21661  continue;
21662  }
21663  if(Time1 < CDTTime) //gone too far so can stop now
21664  {
21665  break;
21666  }
21667  }
21668  }
21669  if(!LocFound) //have to find it in both lists
21670  {
21671  Utilities->CallLogPop(2228);
21672  return( false);
21673  }
21674  //for List2
21675  LocFound = false;
21676  for(ListPtr2 = List2.begin(); ListPtr2 != List2.end(); ListPtr2++)
21677  {
21678  if((*ListPtr2) == Location)
21679  {
21680  LocPtr2 = ListPtr2; //may be modified later
21681  LocFound = true;
21682  }
21683  if(ListPtr2->SubString(1, 3) == "%%%")
21684  {
21685  AnsiString CDTTime = ListPtr2->SubString(4, 5);
21686  //now adjust the time to correspond to the repeat if there is one
21687  if(RepeatNum2 > 0) //if it is 0 then AnsiTime1 is already valid
21688  {
21689  IncMinutes = -1;
21690  FirstServiceTime = TDateTime(-1);
21691  bool BreakFlag = false;
21692  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21693  {
21694  if(TDVIt->ServiceReference == Ref2)
21695  {
21696  if(Ref2Target > Ref2Count)
21697  {
21698  Ref2Count++;
21699  continue;
21700  }
21701  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21702  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21703  {
21704  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21705  {
21706  FirstServiceTime = AVIt->EventTime;
21707  BreakFlag = true;
21708  break;
21709  }
21710  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime)
21711  {
21712  FirstServiceTime = AVIt->ArrivalTime;
21713  BreakFlag = true;
21714  break;
21715  }
21716  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21717  {
21718  FirstServiceTime = AVIt->DepartureTime;
21719  BreakFlag = true;
21720  break;
21721  }
21722  }
21723  if(BreakFlag)
21724  {
21725  break;
21726  }
21727  }
21728  }
21729  if(IncMinutes == -1)
21730  {
21731  throw Exception("IncMinutes -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21732  }
21733  if(FirstServiceTime == TDateTime(-1))
21734  {
21735  throw Exception("First service time -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21736  }
21737  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(61, FirstServiceTime, RepeatNum2, IncMinutes));
21738  }
21739  if(!Arrival && (Time2 == CDTTime)) //continue if equal in case next is a departure for the Location
21740  {
21741  LocFound = false;
21742  continue;
21743  }
21744  if(Arrival && (Time2 == CDTTime)) //gone far enough so can stop
21745  {
21746  break;
21747  }
21748  if(Time2 > CDTTime) //not there yet so go on
21749  {
21750  LocFound = false;
21751  continue;
21752  }
21753  if(Time2 < CDTTime) //gone too far so can stop now
21754  {
21755  break;
21756  }
21757  }
21758  }
21759  if(!LocFound) //have to find it in both lists, and should be found but allow for it not being
21760  {
21761  Utilities->CallLogPop(2229);
21762  return( false);
21763  }
21764  //now, for the arrival analysis, see if there is a common location before the LocPtrs & within any cdts, and if so return true, else return false
21765  //set ListPtr1 to the search start position
21766  if(Arrival)
21767  {
21768  LP1 = List1.begin();
21769  LP1--; //now points to before the first entry
21770  for(ListPtr1 = LocPtr1; ListPtr1 != LP1; ListPtr1--) //search backwards from Location
21771  {
21772  if(ListPtr1 == List1.begin())
21773  {
21774  break;
21775  }
21776  if(ListPtr1->SubString(1, 3) == "%%%") //a cdt event
21777  {
21778  ListPtr1++; //point to one past the cdt
21779  break;
21780  }
21781  }
21782  //set ListPtr2 to the search start position
21783  LP2 = List2.begin();
21784  LP2--; //now points to before the first entry
21785  for(ListPtr2 = LocPtr2; ListPtr2 != LP2; ListPtr2--)
21786  {
21787  if(ListPtr2 == List2.begin())
21788  {
21789  break;
21790  }
21791  if(ListPtr2->SubString(1, 3) == "%%%") //a cdt event
21792  {
21793  ListPtr2++; //point to one past the cdt
21794  break;
21795  }
21796  }
21797  //ListPtr1 & 2 now at search start position
21798  LP1 = ListPtr1;
21799  LP2 = ListPtr2;
21800  //now search forwards, i.e. for common locations before Location
21801  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21802  {
21803  if(ListPtr1 == LocPtr1) //reached Location without finding a common earlier location so skip to the backwards check
21804  {
21805  break;
21806  }
21807  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location
21808  {
21809  break;
21810  }
21811  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21812  {
21813  if(ListPtr2 == LocPtr2) //not found common earlier location so go to the next ListPtr1
21814  {
21815  break;
21816  }
21817  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location so go to the next ListPtr1
21818  {
21819  break;
21820  }
21821  if((*ListPtr1) == (*ListPtr2)) //found a common earlier location
21822  {
21823  Utilities->CallLogPop(2230);
21824  return( true);
21825  }
21826  }
21827  }
21828  }
21829 
21830  //now, for the departure analysis, reset the start positions and search locations after Location
21831 
21832  else
21833  {
21834  LP1 = LocPtr1;
21835  LP1++; //start at one past the location itself
21836  LP2 = LocPtr2;
21837  LP2++;
21838  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21839  {
21840  if(ListPtr1 == List1.end()) //reached end point so stop
21841  {
21842  break;
21843  }
21844  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location
21845  {
21846  break;
21847  }
21848  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21849  {
21850  if(ListPtr2 == List2.end()) //reached end point so go to next ListPtr1
21851  {
21852  break;
21853  }
21854  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location so go to the next ListPtr1
21855  {
21856  break;
21857  }
21858  if((*ListPtr1) == (*ListPtr2)) //found a common later location
21859  {
21860  Utilities->CallLogPop(2231);
21861  return( true);
21862  }
21863  }
21864  }
21865  }
21866  Utilities->CallLogPop(2232);
21867  return( false);
21868 }
21869 
21870 // ---------------------------------------------------------------------------
21871 
21872 AnsiString TTrainController::GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
21873 {
21874  // changed at v2.7.0 to show allowable exit elements
21875  if(ExitList.empty())
21876  {
21877  return("");
21878  }
21879  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetExitLocationAndAt");
21880  AnsiString StartName = Track->TrackElementAt(735, *(ExitList.begin())).ActiveTrackElementName;
21881  AnsiString ExitLocList = "";
21882  AllowedExits = "";
21883 
21884  unsigned int Counter = 0;
21885  for(TNumListIterator ELIt = ExitList.begin(); ELIt != ExitList.end(); ELIt++)
21886  {
21887  ExitLocList += Track->TrackElementAt(1018, *ELIt).ElementID + " ";
21888  Counter++;
21889  if(((Counter % 6) == 0) && (Counter < (ExitList.size() - 1))) // only add a newline if more to come
21890  {
21891  ExitLocList += "\n";
21892  }
21893  }
21894  if(StartName == "")
21895  {
21896  if(ExitList.size() == 1)
21897  {
21898  AnsiString ID = Track->TrackElementAt(738, *(ExitList.begin())).ElementID;
21899  Utilities->CallLogPop(1571);
21900  return(" at " + ID);
21901  }
21902  else
21903  {
21904  Utilities->CallLogPop(1572);
21905  if(ExitList.size() < 4)
21906  {
21907  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21908  return("");
21909  }
21910  else
21911  {
21912  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21913  return("");
21914  }
21915  }
21916  }
21917  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
21918  {
21919  if(Track->TrackElementAt(736, *ELIT).ActiveTrackElementName != StartName)
21920  {
21921  Utilities->CallLogPop(1570);
21922  if(ExitList.size() < 4)
21923  {
21924  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21925  return("");
21926  }
21927  else
21928  {
21929  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21930  return("");
21931  }
21932  }
21933  }
21934  Utilities->CallLogPop(1569);
21935  if(ExitList.size() < 4)
21936  {
21937  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21938  return(" at " + StartName);
21939  }
21940  else
21941  {
21942  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21943  return(" at " + StartName);
21944  }
21945 }
21946 
21947 // ---------------------------------------------------------------------------
21948 /* can't trust this as locations within a vector may not be contiguous
21949  bool TTrainController::IsServiceTerminating(int Caller, TTrainDataEntry *TDEPtr, TActionVectorEntry *AVPtr)
21950  {
21951  //Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
21952  //entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
21953  //must be preceded by a TimeLoc departure
21954  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsServiceTerminating");
21955  for(unsigned int x=1;x<TDEPtr->ActionVector.size();x++)
21956  {
21957  if((AVPtr + x) < TDEPtr->ActionVector.end())
21958  {
21959  AnsiString xx = (AVPtr + x)->Command;//test
21960  TTimetableFormatType xy = (AVPtr + x)->FormatType;//test
21961  TTimetableSequenceType xz = (AVPtr + x)->SequenceType;//test
21962  if(((AVPtr + x)->Command == "Fer") || ((AVPtr + x)->FormatType == TimeLoc))
21963  {
21964  Utilities->CallLogPop();
21965  return false;
21966  }
21967  else if((AVPtr + x)->SequenceType == FinishSequence)
21968  {
21969  Utilities->CallLogPop();
21970  return true;
21971  }
21972  }
21973  }
21974  Utilities->CallLogPop();
21975  return false;
21976  }
21977 */
21978 // ---------------------------------------------------------------------------
21979 
21980 void TTrainController::SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
21981 {
21982  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendPerformanceSummary");
21983  AnsiString FormatStr = "####0.0";
21984  AnsiString AvLateArrMins = "";
21985  AnsiString AvEarlyArrMins = "";
21986  AnsiString AvLatePassMins = "";
21987  AnsiString AvEarlyPassMins = "";
21988  AnsiString AvLateDepMins = "";
21989  AnsiString AvLateExitMins = "";
21990  AnsiString AvEarlyExitMins = "";
21991 
21992  //calculate remaining CumulativeDelayedRandMinsAllTrains for trains still in vector (CumulativeDelayedRandMinsAllTrains for exited or removed trains already accounted for)
21993  for(unsigned int x = 0; x < TrainVector.size(); x++)
21994  {
21995  Utilities->CumulativeDelayedRandMinsAllTrains += int(TrainVectorAt(89, x).CumulativeDelayedRandMinsOneTrain);
21996  }
21997 
21998  if(LateArrivals > 0)
21999  {
22000  AvLateArrMins = FormatFloat(FormatStr, (TotLateArrMins / LateArrivals));
22001  }
22002  if(EarlyArrivals > 0)
22003  {
22004  AvEarlyArrMins = FormatFloat(FormatStr, (TotEarlyArrMins / EarlyArrivals));
22005  }
22006  if(LatePasses > 0)
22007  {
22008  AvLatePassMins = FormatFloat(FormatStr, (TotLatePassMins / LatePasses));
22009  }
22010  if(EarlyPasses > 0)
22011  {
22012  AvEarlyPassMins = FormatFloat(FormatStr, (TotEarlyPassMins / EarlyPasses));
22013  }
22014  if(LateDeps > 0)
22015  {
22016  AvLateDepMins = FormatFloat(FormatStr, (TotLateDepMins / LateDeps));
22017  }
22018  if(LateExits > 0) //added at v2.9.1
22019  {
22020  AvLateExitMins = FormatFloat(FormatStr, (TotLateExitMins / LateExits));
22021  }
22022  if(EarlyExits > 0) //added at v2.9.1
22023  {
22024  AvEarlyExitMins = FormatFloat(FormatStr, (TotEarlyExitMins / EarlyExits));
22025  }
22026  PerfFile << '\n' << '\n' << "***************************************";
22027  PerfFile << '\n' << '\n' << "Performance summary:" << '\n';
22028 
22029  if(OnTimeArrivals != 1)
22030  {
22031  PerfFile << OnTimeArrivals << " on-time arrivals" << '\n';
22032  }
22033  else
22034  {
22035  PerfFile << OnTimeArrivals << " on-time arrival" << '\n';
22036  }
22037  if(LateArrivals > 1)
22038  {
22039  PerfFile << LateArrivals << " late arrivals (average " << AvLateArrMins.c_str() << " min)" << '\n';
22040  }
22041  else if(LateArrivals == 1)
22042  {
22043  PerfFile << LateArrivals << " late arrival (" << AvLateArrMins.c_str() << " min)" << '\n';
22044  }
22045  else
22046  {
22047  PerfFile << LateArrivals << " late arrivals" << '\n';
22048  }
22049  if(EarlyArrivals > 1)
22050  {
22051  PerfFile << EarlyArrivals << " early arrivals (average " << AvEarlyArrMins.c_str() << " min)" << '\n';
22052  }
22053  else if(EarlyArrivals == 1)
22054  {
22055  PerfFile << EarlyArrivals << " early arrival (" << AvEarlyArrMins.c_str() << " min)" << '\n';
22056  }
22057  else
22058  {
22059  PerfFile << EarlyArrivals << " early arrivals" << '\n';
22060  }
22061  if(OnTimePasses != 1)
22062  {
22063  PerfFile << OnTimePasses << " on-time passes" << '\n';
22064  }
22065  else
22066  {
22067  PerfFile << OnTimePasses << " on-time pass" << '\n';
22068  }
22069  if(LatePasses > 1)
22070  {
22071  PerfFile << LatePasses << " late passes (average " << AvLatePassMins.c_str() << " min)" << '\n';
22072  }
22073  else if(LatePasses == 1)
22074  {
22075  PerfFile << LatePasses << " late pass (" << AvLatePassMins.c_str() << " min)" << '\n';
22076  }
22077  else
22078  {
22079  PerfFile << LatePasses << " late passes" << '\n';
22080  }
22081  if(EarlyPasses > 1)
22082  {
22083  PerfFile << EarlyPasses << " early passes (average " << AvEarlyPassMins.c_str() << " min)" << '\n';
22084  }
22085  else if(EarlyPasses == 1)
22086  {
22087  PerfFile << EarlyPasses << " early pass (" << AvEarlyPassMins.c_str() << " min)" << '\n';
22088  }
22089  else
22090  {
22091  PerfFile << EarlyPasses << " early passes" << '\n';
22092  }
22093 
22094  if(OnTimeExits != 1) //this batch added at v2.9.1
22095  {
22096  PerfFile << OnTimeExits << " on-time exits" << '\n';
22097  }
22098  else
22099  {
22100  PerfFile << OnTimeExits << " on-time exit" << '\n';
22101  }
22102  if(LateExits > 1)
22103  {
22104  PerfFile << LateExits << " late exits (average " << AvLateExitMins.c_str() << " min)" << '\n';
22105  }
22106  else if(LateExits == 1)
22107  {
22108  PerfFile << LateExits << " late exit (" << AvLateExitMins.c_str() << " min)" << '\n';
22109  }
22110  else
22111  {
22112  PerfFile << LateExits << " late exits" << '\n';
22113  }
22114  if(EarlyExits > 1)
22115  {
22116  PerfFile << EarlyExits << " early exits (average " << AvEarlyExitMins.c_str() << " min)" << '\n';
22117  }
22118  else if(EarlyExits == 1)
22119  {
22120  PerfFile << EarlyExits << " early exit (" << AvEarlyExitMins.c_str() << " min)" << '\n';
22121  }
22122  else
22123  {
22124  PerfFile << EarlyExits << " early exits" << '\n';
22125  }
22126 
22127  if(OnTimeDeps != 1)
22128  {
22129  PerfFile << OnTimeDeps << " on-time departures" << '\n';
22130  }
22131  else
22132  {
22133  PerfFile << OnTimeDeps << " on-time departure" << '\n';
22134  }
22135  if(LateDeps > 1)
22136  {
22137  PerfFile << LateDeps << " late departures (average " << AvLateDepMins.c_str() << " min)" << '\n';
22138  }
22139  else if(LateDeps == 1)
22140  {
22141  PerfFile << LateDeps << " late departure (" << AvLateDepMins.c_str() << " min)" << '\n';
22142  }
22143  else
22144  {
22145  PerfFile << LateDeps << " late departures" << '\n';
22146  }
22147  TDateTime TempExcessLCDownTime;
22148  for(unsigned int x = 0; x < Track->BarriersDownVector.size(); x++) //added at v2.6.0 - should have been added earlier
22149  {
22150 // if(Track->BarriersDownVector.at(x).ReducedTimePenalty) //assume train still to cross LC as probably will, else have false high value & can have
22151  //later perf summaries with lower values, changed at v2.8.0
22152 // {
22153  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime - TDateTime(180.0 / 86400);
22154 // }
22155 /*
22156  else
22157  {
22158  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime;
22159  }
22160 */
22161  if(TempExcessLCDownTime > TDateTime(0))
22162  {
22163  TrainController->ExcessLCDownMins += (double(TempExcessLCDownTime) * 1440);
22164  }
22165  }
22166 
22167  AnsiString FormattedExcessLCDownMins = FormatFloat(FormatStr, ExcessLCDownMins);
22168 
22169  if(ExcessLCDownMins > 0.1)
22170  {
22171  PerfFile << FormattedExcessLCDownMins.c_str() << " excess minutes of level crossing barrier down time" << '\n';
22172  }
22173  else
22174  {
22175  ExcessLCDownMins = 0; //added at v2.16.1 so doesn't count towards performance score if < 0.1mins (else can have low score with no excess mins recorded)
22176  }
22177  if(Utilities->CumulativeDelayedRandMinsAllTrains > 0) //added at v2.13.0
22178  {
22179  PerfFile << Utilities->CumulativeDelayedRandMinsAllTrains << " minutes lost due to random delays when stopped at locations" << '\n';
22180  }
22181  if(MissedStops != 1)
22182  {
22183  PerfFile << MissedStops << " missed stops" << '\n';
22184  }
22185  else
22186  {
22187  PerfFile << MissedStops << " missed stop" << '\n';
22188  }
22189  if(OtherMissedEvents != 1)
22190  {
22191  PerfFile << OtherMissedEvents << " other missed events" << '\n';
22192  }
22193  else
22194  {
22195  PerfFile << OtherMissedEvents << " other missed event" << '\n';
22196  }
22197  if(SkippedTTEvents != 1)
22198  {
22199  PerfFile << SkippedTTEvents << " skipped timetable events" << '\n';
22200  }
22201  else
22202  {
22203  PerfFile << SkippedTTEvents << " skipped timetable event" << '\n';
22204  }
22205  if(UnexpectedExits != 1)
22206  {
22207  PerfFile << UnexpectedExits << " unexpected train exits" << '\n';
22208  }
22209  else
22210  {
22211  PerfFile << UnexpectedExits << " unexpected train exit" << '\n';
22212  }
22213  if(IncorrectExits != 1)
22214  {
22215  PerfFile << IncorrectExits << " incorrect train exits" << '\n';
22216  }
22217  else
22218  {
22219  PerfFile << IncorrectExits << " incorrect train exit" << '\n';
22220  }
22221  if(NumFailures != 1)
22222  {
22223  PerfFile << NumFailures << " train failures" << '\n';
22224  }
22225  else
22226  {
22227  PerfFile << NumFailures << " train failure" << '\n';
22228  }
22229  if(AvHoursIntValue > 0)
22230  {
22231  if(AvHoursIntValue == 1)
22232  {
22233  PerfFile << AvHoursIntValue << " hour mean time betweeen train failures" << '\n';
22234  }
22235  else
22236  {
22237  PerfFile << AvHoursIntValue << " hours mean time betweeen train failures" << '\n';
22238  }
22239  }
22240  AnsiString AvLateMinsLocsNotReached = "";
22241 
22243  int LocsNotReached = (NotStartedTrainLateArr + OperatingTrainLateArr); //dropped divide by 2 after 2.7.0 as don't count late departures as 'failed to arrive'
22244  // each location has an arrival and departure (generally) so divide by 2 - no, dropped after 2.7.0
22245 
22246  if(LocsNotReached > 0)
22247  {
22248  AvLateMinsLocsNotReached = FormatFloat(FormatStr, (OperatingTrainLateMins + NotStartedTrainLateMins) / (NotStartedTrainLateArr + OperatingTrainLateArr));
22249  PerfFile << LocsNotReached << " locations that trains failed to reach (average lateness " << AvLateMinsLocsNotReached.c_str() << " min)" << '\n';
22250  }
22251  if(SPADRisks != 1)
22252  {
22253  PerfFile << SPADRisks << " SPAD risks" << '\n';
22254  }
22255  else
22256  {
22257  PerfFile << SPADRisks << " SPAD risk" << '\n';
22258  }
22259  if(SPADEvents != 1)
22260  {
22261  PerfFile << SPADEvents << " SPADs" << '\n';
22262  }
22263  else
22264  {
22265  PerfFile << SPADEvents << " SPAD" << '\n';
22266  }
22267  if(Derailments != 1)
22268  {
22269  PerfFile << Derailments << " derailments" << '\n';
22270  }
22271  else
22272  {
22273  PerfFile << Derailments << " derailment" << '\n';
22274  }
22275  if(CrashedTrains != 1)
22276  {
22277  PerfFile << CrashedTrains << " crashed trains" << '\n';
22278  }
22279  else
22280  {
22281  PerfFile << CrashedTrains << " crashed train" << '\n';
22282  }
22283  PerfFile << '\n' << "***************************************" << '\n';
22284 
22285  bool DerailSPADFlag = false, CrashFlag = false;
22286 
22287  int OverallScorePercent = 100;
22288  int TotArrDepExit = 0;
22289  double TotLateMinsFactor = 1;
22290  double MissedStopAndSPADRiskFactor = 1;
22291  double NetNegFactor = 1;
22292 
22294  EarlyExits + LateExits + OnTimeExits; //exits added at v2.9.1, passes not counted
22295  // TotArrDep: total number of arrivals & departures including those for trains that haven't reached their destinations yet and are late
22296  // changed at v1.1.4 - calc was inside "if(OverallScorePercent == 100).." block so could remain 0 for SPADs & crashes, & then received the
22297  // 'no timetabled departures... message, which was inappropriate
22298 
22299  if((SPADEvents > 0) || (Derailments > 0))
22300  {
22301  OverallScorePercent = 5; // overrides other calculations
22302  DerailSPADFlag = true;
22303  }
22304  if(CrashedTrains > 0)
22305  {
22306  OverallScorePercent = 0; // overrides other calculations
22307  CrashFlag = true;
22308  }
22309  if(OverallScorePercent == 100)
22310  {
22311  int LatenessPenalty = TotLateArrMins + TotLateDepMins; //added at v2.13.0 for random delays
22312  if(Utilities->CumulativeDelayedRandMinsAllTrains > LatenessPenalty)
22313  {
22314  LatenessPenalty = 0;
22315  }
22316  else
22317  {
22318  LatenessPenalty -= Utilities->CumulativeDelayedRandMinsAllTrains;
22319  }
22320  if(TotArrDepExit > 0)
22321  {
22322  TotLateMinsFactor = exp((-0.1732) * (LatenessPenalty + OperatingTrainLateMins + NotStartedTrainLateMins + TotLateExitMins +
22323  ((OtherMissedEvents + SkippedTTEvents + UnexpectedExits + ExcessLCDownMins) * 15)) / TotArrDepExit); //exits added at v2.9.1
22324  // TotLateMinsFactor: negative exponential factor based on overall average arr & dep minutes late (with OtherMissedEvents & UnexpectedExits
22325  // counting as 15 mins late each), where 4 mins late average = half, 8 mins late = a quarter etc
22326  MissedStopAndSPADRiskFactor = exp((-17.33) * (MissedStops + SPADRisks + IncorrectExits) / TotArrDepExit);
22327  // MissedEventAndSPADRiskFactor: negative exponential factor based on number of missed stops, SPAD risks & IncorrectExits as a proportion
22328  // of arrivals & departures, where 4% = half, 8% = a quarter etc
22329  NetNegFactor = TotLateMinsFactor * MissedStopAndSPADRiskFactor;
22330  // NetNegfactor: product of the above two
22331  OverallScorePercent = 100 * NetNegFactor;
22332  }
22333  }
22334  if((TotArrDepExit > 0) || DerailSPADFlag || CrashFlag)
22335  // flag condits added at v1.1.4 - see above for what the error was
22336  {
22337  AnsiString OneFailureString = ", though the failure would account for some poor performance";
22338  AnsiString TwoOrMoreFailureString = ", though the failures would account for some poor performance";
22339  AnsiString AddedString = "";
22340  if(NumFailures == 1)
22341  {
22342  AddedString = OneFailureString;
22343  }
22344  if(NumFailures > 1)
22345  {
22346  AddedString = TwoOrMoreFailureString;
22347  }
22348  PerfFile << "\nOverall score: " << OverallScorePercent << "%\n";
22349  AnsiString Rating = "";
22350  if(OverallScorePercent == 100)
22351  {
22352  Rating = "Perfect!";
22353  }
22354  else if(OverallScorePercent >= 95)
22355  {
22356  Rating = "Excellent";
22357  }
22358  else if(OverallScorePercent >= 90)
22359  {
22360  Rating = "Very good";
22361  }
22362  else if(OverallScorePercent >= 80)
22363  {
22364  Rating = "Good";
22365  }
22366  else if(OverallScorePercent >= 70)
22367  {
22368  Rating = "Fair";
22369  }
22370  else if(OverallScorePercent >= 60)
22371  {
22372  Rating = "Unacceptable" + AddedString;
22373  }
22374  else if(OverallScorePercent >= 50)
22375  {
22376  Rating = "Poor" + AddedString;
22377  }
22378  else if(OverallScorePercent >= 40)
22379  {
22380  Rating = "Bad" + AddedString;
22381  }
22382  else if(OverallScorePercent >= 30)
22383  {
22384  Rating = "Very bad" + AddedString;
22385  }
22386  else if(OverallScorePercent >= 20)
22387  {
22388  Rating = "Terrible" + AddedString;
22389  }
22390  else if(OverallScorePercent >= 10)
22391  {
22392  Rating = "Appalling" + AddedString;
22393  }
22394  else if(OverallScorePercent >= 5)
22395  {
22396  if(DerailSPADFlag)
22397  {
22398  Rating = "Disastrous - potential loss of life";
22399  }
22400  // SPADs/Derailments
22401  else
22402  {
22403  Rating = "Dire" + AddedString;
22404  }
22405  }
22406  else if(OverallScorePercent < 5)
22407  {
22408  if(CrashFlag)
22409  {
22410  Rating = "Catastrophic - loss of life"; // Crashes
22411  }
22412  else
22413  {
22414  Rating = "Abysmal";
22415  }
22416  }
22417  PerfFile << "Overall rating: " << Rating.c_str() << '\n';
22418  }
22419  else
22420  {
22421  PerfFile << "\nThere were no timetabled departures, arrivals or exits so there is insufficient information to provide a performance score or rating" << '\n';
22422  }
22423  PerfFile << '\n' << "***************************************";
22424  PerfFile.flush();
22425  Utilities->CallLogPop(1736);
22426 }
22427 
22428 // ---------------------------------------------------------------------------
22429 
22431 {
22432  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetWarningFlags");
22433  for(unsigned int x = 0; x < TrainVector.size(); x++)
22434  {
22435  TTrain &Train = TrainVectorAt(58, x);
22436  if(Train.Crashed)
22437  // can't use background colours for crashed & derailed because same colour
22438  {
22439  CrashWarning = true;
22440  }
22441  else if(Train.Derailed)
22442  // can't use background colours for crashed & derailed because same colour
22443  {
22444  DerailWarning = true;
22445  }
22446  else if(Train.BackgroundColour == clSPADBackground)
22447  // use colour as that changes as soon as passes signal
22448  {
22449  SPADWarning = true;
22450  }
22451  else if(Train.BackgroundColour == clTrainFailedBackground)
22452  {
22453  TrainFailedWarning = true;
22454  }
22455  else if(Train.BackgroundColour == clCallOnBackground)
22456  // use colour as also stopped at signal
22457  {
22458  CallOnWarning = true;
22459  }
22460  else if(Train.BackgroundColour == clSignalStopBackground)
22461  // use colour to distinguish from call-on
22462  {
22463  SignalStopWarning = true;
22464  }
22465  else if(Train.BackgroundColour == clBufferAttentionNeeded)
22466  // use colour to distinguish from ordinary buffer stop
22467  {
22468  BufferAttentionWarning = true;
22469  }
22470  }
22471  Utilities->CallLogPop(1796);
22472 }
22473 
22474 // ---------------------------------------------------------------------------
22475 
22477 {
22478  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcSignalStopLateness");
22479 
22480  // calculate lateness for running trains
22483  for(unsigned int x = 0; x < TrainVector.size(); x++)
22484  {
22485  TTrain &Train = TrainVectorAt(64, x);
22486  for(TActionVectorEntry * AVEntryPtr = &Train.TrainDataEntryPtr->ActionVector.front(); AVEntryPtr < &Train.TrainDataEntryPtr->ActionVector.back();
22487  AVEntryPtr++)
22488  {
22489  if(AVEntryPtr < Train.ActionVectorEntryPtr)
22490  {
22491  continue;
22492  }
22493  if((AVEntryPtr->ArrivalTime > TDateTime(-1)) && !Train.RevisedStoppedAtLoc() && (GetRepeatTime(42, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22494  TTClockTime))
22495  {
22496  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(43, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes));
22498  }
22499 /* dropped departures after 2.7.0 because these don't count for 'failed to reach' numbers
22500  if((AVEntryPtr->DepartureTime > TDateTime(-1)) && (GetRepeatTime(44, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22501  TTClockTime))
22502  {
22503  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(45, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes));
22504  OperatingTrainArrDep++;
22505  }
22506 */
22507  }
22508  }
22509 
22510  // calculate lateness for trains that haven't started yet (could be held awaiting entry)
22513 
22514  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
22515  {
22516  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
22517  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
22518  int IncrementalMinutes = 0;
22519  if(AVEntryLast.FormatType == Repeat)
22520  {
22521  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
22522  }
22523  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
22524  {
22525  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
22526  if(TTOD.RunningEntry != NotStarted)
22527  {
22528  continue;
22529  }
22530  // note that can't rely on the above for sessionfiles saved before v0.6b as wasn't set to Running for Sns/Fsp/rsp & shuttles
22531  // but if trains had exited then would be set to Exited, so need to check against trains still operating - use the test below
22532  bool TrainOperatingFlag = false;
22533  for(unsigned int a = 0; a < TrainController->TrainVector.size(); a++)
22534  {
22535  if((TrainController->TrainVector.at(a).TrainDataEntryPtr == &TDEntry) && (TrainController->TrainVector.at(a).RepeatNumber == y))
22536  {
22537  TrainOperatingFlag = true;
22538  break;
22539  }
22540  }
22541  if(TrainOperatingFlag)
22542  {
22543  continue;
22544  }
22545  if(GetRepeatTime(46, TDEntry.ActionVector.at(0).EventTime, y, IncrementalMinutes) > TTClockTime)
22546  {
22547  break; // if the first time is greater than TTClockTime then all the rest will also be greater (& default of -1 will be less so will be ignored)
22548  }
22549  for(unsigned int z = 0; z < TDEntry.ActionVector.size(); z++)
22550  {
22551  TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
22552  if(GetRepeatTime(35, AVEntry.EventTime, y, IncrementalMinutes) > TTClockTime)
22553  {
22554  break; // all the rest will also be greater (& default of -1 will be less)
22555  }
22556  if(GetRepeatTime(36, AVEntry.ArrivalTime, y, IncrementalMinutes) > TTClockTime)
22557  {
22558  break; // all the rest will also be greater (& default of -1 will be less)
22559  }
22560  if(GetRepeatTime(37, AVEntry.DepartureTime, y, IncrementalMinutes) > TTClockTime)
22561  {
22562  break; // all the rest will also be greater (& default of -1 will be less)
22563  }
22564  if((AVEntry.ArrivalTime > TDateTime(-1)) && (GetRepeatTime(38, AVEntry.ArrivalTime, y, IncrementalMinutes) < TTClockTime))
22565  {
22566  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(39, AVEntry.ArrivalTime, y, IncrementalMinutes));
22568  }
22569 /* dropped departures after 2.7.0 as only interested in 'failed to reach' number - if train hasn't arrived then it hasn't departed so shouldn't count that as part of 'failed to reach'
22570  if((AVEntry.DepartureTime > TDateTime(-1)) && (GetRepeatTime(40, AVEntry.DepartureTime, y, IncrementalMinutes) < TTClockTime))
22571  {
22572  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(41, AVEntry.DepartureTime, y, IncrementalMinutes));
22573  NotStartedTrainArrDep++;
22574  }
22575 */
22576  }
22577  }
22578  }
22579  Utilities->CallLogPop(1894);
22580 }
22581 
22582 // ---------------------------------------------------------------------------
22583 
22585 // new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2.13.0)
22586 // clears entries then adds values for running trains then for continuation entries
22587 // dont limit size here as need to check all trains (ActionsDueListBox is limited to 20 trains in Interface.cpp)
22588 {
22589  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildOpTimeToActMultimap");
22590  OpTimeToActMultiMap.clear();
22591  TOpTimeToActMultiMapEntry OpTimeToActMultiMapEntry;
22592 
22593  if(!TrainVector.empty())
22594  // build OpTimeToActMultiMap entries for running trains
22595  {
22596  AnsiString HeadCode;
22597  // dropped in favour of TrainID for running trains int VecPos; //TrackVectorPosition of LeadElement or continuation where train is to enter
22598  int TrainID;
22599  THCandTrainPosParam HCandTrainPosParam;
22600  for(unsigned int x = 0; x < TrainVector.size(); x++)
22601  {
22602  HeadCode = TrainVectorAt(62, x).HeadCode;
22603  TrainID = TrainVectorAt(63, x).TrainID;
22604  HCandTrainPosParam.first = HeadCode;
22605  HCandTrainPosParam.second = TrainID;
22606  float TimeToAct = TrainVectorAt(65, x).OpTimeToAct;
22607  if((TimeToAct >= 0) && (TimeToAct < 59.9))
22608  // -1 indicates don't display
22609  {
22610  OpTimeToActMultiMapEntry.first = TimeToAct;
22611  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22612  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22613  }
22614  }
22615  }
22616 /*
22617  * class TContinuationTrainExpectationEntry
22618  {
22619  public:
22620  AnsiString Description; ///< service description
22621  AnsiString HeadCode; ///< service headcode
22622  int RepeatNumber; ///< service RepeatNumber
22623  int IncrementalMinutes; ///< Repeat separation in minutes
22624  int IncrementalDigits; ///< Repeat headcode separation
22625  int VectorPosition; ///< TrackVectorPosition for the continuation element
22626  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
22627  };
22628 
22629  Multimap class for TContinuationTrainExpectationEntry objects, where the access key is the expectation time
22630  typedef std::multimap<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
22631  typedef TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator; ///< iterator for the multimap
22632  typedef std::pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair; ///< a single multimap entry
22633 */
22634 
22636  // build OpTimeToActMultiMap entries for expected trains
22637  {
22638  // note that using the ContinuationTrainExpectationMultiMap automatically ensures that entries are in ascending time order
22639  // first have to calculate times to red signal for each train due to enter (ignore later trains as will likely change before they are due)
22640  float TimeToAct = 0; // minutes
22641  int DistanceToRedSignal = 0; // metres
22642  TContinuationEntryVecPosVector ContinuationEntryVecPosVector;
22643  // used to ensure only one train displayed for a given continuation
22644  ContinuationEntryVecPosVector.clear();
22645  bool LaterTrain = false;
22648  {
22649  LaterTrain = false;
22650  if(CTEIt->second.TrainDataEntryPtr->TrainOperatingDataVector.at(CTEIt->second.RepeatNumber).RunningEntry != NotStarted)
22651  {
22652  CTEIt++;
22653  continue; // not interested in running or exited trains
22654  }
22655  if(Track->TrackElementAt(934, CTEIt->second.VectorPosition).TrainIDOnElement > 0)
22656  {
22657  CTEIt++;
22658  continue;
22659  // don't include trains not entered yet when a train is already on the continuation
22660  }
22661  if(!ContinuationEntryVecPosVector.empty())
22662  {
22663  for(unsigned int x = 0; x < ContinuationEntryVecPosVector.size(); x++)
22664  {
22665  if(CTEIt->second.VectorPosition == ContinuationEntryVecPosVector.at(x))
22666  {
22667  LaterTrain = true;
22668  ;
22669  // skip past remaining trains waiting to enter at same point
22670  break;
22671  }
22672  }
22673  }
22674  if(LaterTrain)
22675  {
22676  CTEIt++;
22677  continue;
22678  }
22679  ContinuationEntryVecPosVector.push_back(CTEIt->second.VectorPosition);
22680  AnsiString HeadCode = CTEIt->second.HeadCode;
22681  float CurrentStopTime; // set to 0 at start of function
22682  float LaterStopTime; // set to 0 at start of function
22683  float RecoverableTime; // set to 0 at start of function
22684  int AvTrackSpeed; // set to 0 at start of function
22685  int TrainID = -1; // not yet allocated for train still to enter
22686  int DistanceToExit; //not used for continuation entries
22687  THVShortPair ExitPair;
22688  bool SigControlAndCanPassRedSignal = false; // doesn't apply for a continuation
22689 
22690 //at v2.11.0 found that with *AVPtr set to ...ActionVector.at(0) below instead of ...at(1) to stop signaller control trains throwing an error (because there is no ...at(1) -
22691 //discovered with Birmingham) the LaterStopTime isn't calculated and if a train does something other than depart after an arrival it is still listed in the actions due panel -
22692 //because it just calcs the distance to the red signal and converts that to a time. So here (after v2.11.0) this new test is introduced to determine whether a train is a
22693 //signaller control train (when the ActionVector size is 1) or not (ActionVector size > 1), and the ...at(value) is set accordingly - 0 for signaller control or 1 if not.
22694 
22695  int AtValue = 1;
22696  if(CTEIt->second.TrainDataEntryPtr->ActionVector.size() == 1)
22697  {
22698  AtValue = 0;
22699  }
22700  DistanceToRedSignal = CalcDistanceToRedSignalandStopTime(1, CTEIt->second.VectorPosition, 0,
22701  // EntryPos always 0 for entering at a continuation
22702  SigControlAndCanPassRedSignal, &CTEIt->second.TrainDataEntryPtr->ActionVector.at(AtValue), //see above note
22703  HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
22704  // for above VectorPosition is the first element to have its length included in the sum, so for a continuation it's the continuation itself
22705  // for a train it's the one in front of LeadElement
22706  if(AvTrackSpeed < 30)
22707  {
22708  AvTrackSpeed = 30;
22709  }
22710  if(DistanceToRedSignal == -1)
22711  {
22712  TimeToAct = 60.0;
22713  }
22714  else
22715  {
22716  int Speed = AvTrackSpeed;
22717  int MaxSpeed = int(CTEIt->second.TrainDataEntryPtr->MaxRunningSpeed);
22718  if(AvTrackSpeed > MaxSpeed)
22719  {
22720  Speed = MaxSpeed;
22721  }
22722  if(CTEIt->second.TrainDataEntryPtr->ActionVector.at(0).SignallerControl) //changed to ...at(0) from at(1) at v2.11.0 as SignallerControl only valid for ..at(0)
22723  // defined in timetable as under signaller control
22724  {
22725  Speed = CTEIt->second.TrainDataEntryPtr->SignallerSpeed;
22726  LaterStopTime = 0;
22727  }
22728  TimeToAct = LaterStopTime + DistanceToRedSignal * 3.6 / 60 / Speed;
22729  // accel & decel taken into account in
22730  // CalcDistanceToRedSignalandStopTime
22731  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
22732  // don't need CurrentStopTime or RecoverableTime for continuation entries
22733  float MinsBefEnter = double(CTEIt->first - TTClockTime) * 86400.0 / 60.0;
22734  TimeToAct += MinsBefEnter;
22735  }
22736  THCandTrainPosParam HCandTrainPosParam;
22737  HCandTrainPosParam.first = HeadCode;
22738  HCandTrainPosParam.second = -1 - CTEIt->second.VectorPosition;
22739  // -1-CTE... because 2nd value covers TrainID if +ve &
22740  // continuation track vector position if -ve, -1 allows for vecpos being 0
22741  if(TimeToAct < 59.9) // if 60 don't enter a value in multimap
22742  {
22743  OpTimeToActMultiMapEntry.first = TimeToAct;
22744  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22745  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22746  }
22747  CTEIt++;
22748  }
22749  }
22750  Utilities->CallLogPop(2081);
22751 }
22752 
22753 // ---------------------------------------------------------------------------
22754 
22756 // new for multiplayer
22757 // clears entries then adds values for running trains
22758 {
22759  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildTimeToExitMultiMap");
22760  TimeToExitMultiMap.clear();
22761  TTimeToExitMultiMapEntry TimeToExitMultiMapEntry;
22762 
22763  if(!TrainVector.empty())
22764  // build map entries for running trains
22765  {
22766  TExitInfo ExitInfo; //corresponds to TServiceInfo in Interface
22767  THVShortPair ExitPair;
22768  float TimeToExit;
22769  for(unsigned int x = 0; x < TrainVector.size(); x++)
22770  {
22772  ExitInfo.RepeatNumber = short(TrainVectorAt(81, x).RepeatNumber);
22773  ExitInfo.TimeToExitSecs = short(TrainVectorAt(77, x).TimeToExit * 60);
22774  ExitPair = TrainVectorAt(76, x).ExitPair;
22775  if((ExitInfo.TimeToExitSecs >= 3570) || (ExitInfo.TimeToExitSecs < 1)) //59.5 mins or -60 secs
22776  {
22777  ExitInfo.TimeToExitSecs = -1;
22778  }
22779  TimeToExitMultiMapEntry.first = ExitPair;
22780  TimeToExitMultiMapEntry.second = ExitInfo;
22781  TimeToExitMultiMap.insert(TimeToExitMultiMapEntry);
22782  }
22783  }
22784  Utilities->CallLogPop(2323);
22785 }
22786 
22787 // ---------------------------------------------------------------------------
22788 
22789 int TTrainController::CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos,
22790  bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime,
22791  float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
22792 // new v2.2.0
22793 // vectorPosition is the value for the first element to be measured - for a continuation it's the continuation itself, for a train
22794 // it's the one after LeadElement, returns -1 for infinity - i.e. not approaching red signal (e.g. maybe cdt before reach it).
22795 // CurrentStopTime is the time to depart from the current station (if stopped at a station) and LaterStopTime is the total station
22796 // stop times for stations after the current one. DistanceToRedSignal is what the name implies, if -1 is returned the other values
22797 // aren't used - this means there is no display for the train in question
22798 {
22799  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DistanceToRedSignal, " + AnsiString(TrackVectorPosition) + ", " +
22800  AnsiString(TrackVectorPositionEntryPos) + ", " + AVPtr->Command);
22801  int DistanceToRedSignal = 0;
22802  DistanceToExit = -1;
22803  ExitPair.first = -1;
22804  ExitPair.second = -1;
22805  int CumTrackSpeed = 0;
22806  // average track speed, in case need to use in time calc
22807  int TrackSpeedCount = 0;
22808  float KmPerLocationStop;
22809  float MaxAllowableSpeed;
22810 
22811  //below added at v2.6.1
22812  if(TrainID > -1) //will be -1 for trains not entered yet
22813  {
22814  TTrain &Train = TrainVectorAtIdent(51, TrainID);
22815  Train.DistanceToStationStop = 0; //if find a red signal first then this distance isn't needed
22816  Train.StationStopCalculated = false;
22817  }
22818  AvTrackSpeed = 0;
22819  int CurrentElement = TrackVectorPosition;
22820  int CurrentEntryPos = TrackVectorPositionEntryPos;
22821  int NextElement;
22822  int NextEntryPos;
22823  int NextExitPos;
22824 
22825  CurrentStopTime = 0;
22826  LaterStopTime = 0;
22827  RecoverableTime = 0;
22828  if(CurrentElement == -1) // train on end element, no action needed
22829  {
22830  Utilities->CallLogPop(2094);
22831  return(-1);
22832  }
22833  int CurrentExitPos;
22834 
22835  // get ExitPos for first element to be measured
22836  if(Track->TrackElementAt(935, CurrentElement).TrackType == Points)
22837  {
22838  if((CurrentEntryPos == 0) || (CurrentEntryPos == 2)) // leading point
22839  {
22840  if(Track->TrackElementAt(936, CurrentElement).Attribute == 0)
22841  {
22842  CurrentExitPos = 1;
22843  }
22844  else
22845  {
22846  CurrentExitPos = 3;
22847  }
22848  }
22849  else
22850  {
22851  CurrentExitPos = 0; // trailing point
22852  }
22853  }
22854  else
22855  {
22856  CurrentExitPos = Track->GetNonPointsOppositeLinkPos(CurrentEntryPos);
22857  }
22858  // get CumTrackSpeed for first measured element
22859 
22860  TConfiguration CurrentExitConfig = Track->TrackElementAt(937, CurrentElement).Config[CurrentExitPos];
22861  int CurrentAttribute = Track->TrackElementAt(938, CurrentElement).Attribute;
22862  bool CurrentElementFailed = Track->TrackElementAt(1549, CurrentElement).Failed; //added at v2.13.2
22863 
22864  // check if currently stopped at a location, and if so add the remaining dwell time
22865  // can't use CurrentElement as that is in front of LeadElement and might not be at the location
22866  if(TrainID > -1)
22867  // -1 for a continuation and can't be at a location as not yet entered
22868  {
22869  TTrain &Train = TrainVectorAtIdent(39, TrainID); //Train wasn't a reference before v2.6.1 mods so FirstLaterStopRecoverableTime wouldn't be reset for the referenced train
22871  // this used to deduct from RecoverableTime when arrive at a location
22872  if(Train.RevisedStoppedAtLoc())
22873  {
22874  if(Train.StoppedForTrainInFront || Train.TrainInFront)
22875  {
22876  Utilities->CallLogPop(2082);
22877  return(-1); // no action needed
22878  }
22880  { //added '|| (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)' at v2.16.1 so description ignored in calculating action due time
22881  //added TimeCmdMaxSpeed at v2.21.0
22882  Utilities->CallLogPop(2083);
22883  return(-1); // not due a departure, description change or a max speed change so no action needed
22884  }
22885  else if(((Train.ActionVectorEntryPtr + 1)->FormatType == TimeLoc) && ((Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription) || Train.ActionVectorEntryPtr->FormatType == TimeCmdMaxSpeed)) // due a departure immediately after change of description or change of max speed
22886  { //added at v2.16.1 to cover description change due next then a departure //added TimeCmsMaxSpeed at v2.21.0
22887  double TimeToDepart = double((Train.GetTrainTime(68, (Train.ActionVectorEntryPtr + 1)->DepartureTime)) - TrainController->TTClockTime) * 86400 / 60; // mins to depart excluding possible 30sec allowance from LastActionTime
22888  //need repeat time for the above
22889  if((Train.ActionVectorEntryPtr + 1)->DepartureTime == Train.ActionVectorEntryPtr->EventTime) //don't need repeat time here
22890  {
22891  TimeToDepart+= 0.5; //add in the 30 secs if depature time same as description or max speed change time
22892  }
22893  if(TimeToDepart < 0.5)
22894  {
22895  TimeToDepart = 0.5;
22896  }
22897  // can't convert a TDateTime to a float directly
22898  CurrentStopTime = float(TimeToDepart);
22899  AVPtr++;
22900  AVPtr++;
22901  }
22902  else if((Train.ActionVectorEntryPtr->FormatType == TimeLoc) || (Train.ActionVectorEntryPtr->FormatType == TimeTimeLoc)) // due a departure as next action
22903  {
22904  double TimeToDepart = double(Train.ReleaseTime - TrainController->TTClockTime) * 86400 / 60; // mins to depart
22905  // can't convert a TDateTime to a float directly
22906  CurrentStopTime = float(TimeToDepart);
22907  AVPtr++;
22908  }
22909  else //added at v2.16.1 to catch all other combinations
22910  { //none of the above so no action needed
22911  Utilities->CallLogPop(2628);
22912  return(-1);
22913  }
22914  }
22915  }
22916  // check if CurrentElement is a red signal, but ok if autosig route after provided signal not failed
22917  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22918  // ok if autosig route after red signal unless signal has failed
22919  {
22920  int NextElement = Track->TrackElementAt(939, CurrentElement).Conn[CurrentExitPos];
22921  int NextEntryPos = Track->TrackElementAt(940, CurrentElement).ConnLinkPos[CurrentExitPos];
22922  int RouteNumber; // holder for referenced value, not used
22923  if((AllRoutes->GetRouteTypeAndNumber(33, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22924  { //CurrentElementFailed added at v2.13.2
22925  Utilities->CallLogPop(2078);
22926  return(-1);
22927  }
22928  else if(SigControlAndCanPassRedSignal)
22929  // ignore signal and increment CurrentElement to NextElement
22930  {
22931  if(Track->TrackElementAt(941, NextElement).TrackType == Points)
22932  {
22933  if((NextEntryPos == 0) || (NextEntryPos == 2))
22934  // leading entry point
22935  {
22936  if(Track->TrackElementAt(942, NextElement).Attribute == 0)
22937  {
22938  NextExitPos = 1;
22939  }
22940  else
22941  {
22942  NextExitPos = 3;
22943  }
22944  }
22945  else
22946  {
22947  NextExitPos = 0; // trailing entry point
22948  }
22949  }
22950  else
22951  {
22952  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22953  }
22954  CurrentElement = NextElement;
22955  CurrentEntryPos = NextEntryPos;
22956  CurrentExitPos = NextExitPos;
22957  CurrentExitConfig = Track->TrackElementAt(943, CurrentElement).Config[CurrentExitPos];
22958  CurrentAttribute = Track->TrackElementAt(944, CurrentElement).Attribute;
22959  }
22960  else if((TrainID > -1) && (TrainVectorAtIdent(40, TrainID).TrainMode == Timetable)) // ignore signallercontrol or will
22961  // give 'NOW' indication after allowed to pass stop signal when LeadMidLag (AllowedToPassRedSignal reset by this point)
22962  {
22963  Utilities->CallLogPop(2084);
22964  return(0);
22965  // stopped with red signal in front, don't need AvSpeedLimit in this case, & if at location awaiting departure dwell time already calculated
22966  }
22967  }
22968  int LaterStopNumber = 0;
22969  int x = 0;
22970  // added in v2.4.0 to prevent endless circling round track loops - spotted by Xeon 09/03/20 & reported by emsil
22971 
22972  while(!((CurrentExitConfig == Signal) && (CurrentAttribute == 0)))
22973  // not red signal next (in fwd direction) so enter loop to calc CumLength
22974  {
22975  x++; // added in v2.4.0 as above
22976  if(x > 5000)
22977  {
22978  Utilities->CallLogPop(2120);
22979  return(-1);
22980  }
22981  if(CurrentEntryPos > 1)
22982  {
22983  DistanceToRedSignal += Track->TrackElementAt(916, CurrentElement).Length23;
22984  CumTrackSpeed += Track->TrackElementAt(945, CurrentElement).SpeedLimit23;
22985  }
22986  else
22987  {
22988  DistanceToRedSignal += Track->TrackElementAt(917, CurrentElement).Length01;
22989  CumTrackSpeed += Track->TrackElementAt(946, CurrentElement).SpeedLimit01;
22990  }
22991  TrackSpeedCount++;
22992 
22993  //added for multiplayer - exiting at a continuation and continuation length already added
22994  if((Track->TrackElementAt(1407, CurrentElement).TrackType == Continuation) && (Track->TrackElementAt(1408, CurrentElement).Config[CurrentExitPos] == End))
22995  {
22996  DistanceToExit = DistanceToRedSignal; //don't need to exit function here as will exit when find that the next Conn value is -1
22997  ExitPair.first = Track->TrackElementAt(1409, CurrentElement).HLoc;
22998  ExitPair.second = Track->TrackElementAt(1410, CurrentElement).VLoc;
22999  //here repeat calcs for MaxAllowableSpeed & AvTrackSpeed as done at end for stop signal
23000  //need here as next element will be -1 so will exit before calcs at end
23001  if(TrackSpeedCount > 0)
23002  {
23003  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
23004  }
23005  else // shouldn't reach here but include to prevent divide by zero error
23006  {
23007  if(CurrentEntryPos > 1)
23008  {
23009  MaxAllowableSpeed = Track->TrackElementAt(951, CurrentElement).SpeedLimit23;
23010  }
23011  else
23012  {
23013  MaxAllowableSpeed = Track->TrackElementAt(952, CurrentElement).SpeedLimit01;
23014  }
23015  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
23016  }
23017  //calc AvTrackSpeed
23018  if(LaterStopNumber > 0)
23019  {
23020  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
23021  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
23022  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
23023  // average line speed/2 (for half distance accelerating and half decelerating.
23024  }
23025  else
23026  {
23027  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
23028  // using linear trendline for accel & decel distance at various speeds
23029  // at half braking, speed never < 60 using this
23030  }
23031  if(AvTrackSpeed > MaxAllowableSpeed)
23032  {
23033  AvTrackSpeed = MaxAllowableSpeed;
23034  }
23035  }
23036 
23037  // added at v2.6.1 to find DistanceToStationStop for trains running early
23038  if(TrainID > -1) //can ignore continuation entries as these don't run early
23039  {
23040  TTrain &Train = TrainVectorAtIdent(52, TrainID);
23041  if(!Train.StationStopCalculated)
23042  {
23043  if(Train.TrainMode == Timetable)
23044  {
23045  bool StopRequired = false;
23046  if(!Train.TimetableFinished && (Train.NameInTimetableBeforeCDT(16, Track->TrackElementAt(1005, CurrentElement).ActiveTrackElementName,
23047  StopRequired) > -1) && ((Track->TrackElementAt(1006, CurrentElement).StationEntryStopLinkPos1 == CurrentEntryPos) ||
23048  (Track->TrackElementAt(1010, CurrentElement).StationEntryStopLinkPos2 == CurrentEntryPos) ||
23049  (Track->TrackElementAt(1654, CurrentElement).StationEntryStopLinkPos3 == CurrentEntryPos) ||
23050  (Track->TrackElementAt(1655, CurrentElement).StationEntryStopLinkPos4 == CurrentEntryPos)))
23051  {
23052  // no need to add in the length of element to CumulativeLength
23053  if(StopRequired)
23054  {
23055  Train.DistanceToStationStop = DistanceToRedSignal; // DistanceToRedSignal holds the intermediate distance to this point
23056  Train.StationStopCalculated = true; //don't want to update it with later stops
23057  }
23058  }
23059  }
23060  }
23061  }
23062  // check for train in front, but if on a bridge on other track then ok
23063  TTrackElement TE = Track->TrackElementAt(947, CurrentElement);
23064  int TrainOnElement;
23065  if(TE.TrackType != Bridge)
23066  {
23067  TrainOnElement = TE.TrainIDOnElement;
23068  }
23069  else
23070  {
23071  if(CurrentEntryPos > 1)
23072  {
23074  }
23075  else
23076  {
23078  }
23079  }
23080  if((TrainOnElement > -1) && (TrainOnElement != TrainID))
23081  // train in front before red signal
23082  {
23083  Utilities->CallLogPop(2085);
23084  return(-1);
23085  }
23086  // add to stoptime if required
23087  if(Track->TrackElementAt(948, CurrentElement).ActiveTrackElementName != "")
23088  {
23089  double StopTimeDouble;
23090  while(AVPtr->FormatType == PassTime)
23091  {
23092  AVPtr++; // skip past any passes
23093  }
23094  if((Track->TrackElementAt(949, CurrentElement).ActiveTrackElementName == AVPtr->LocationName) && ((AVPtr->FormatType == TimeLoc) ||
23095  (AVPtr->FormatType == TimeTimeLoc)))
23096  // stop due here so calc dwell time & advance Ptr
23097  {
23098  if(AVPtr->FormatType == TimeTimeLoc)
23099  {
23100  LaterStopNumber++;
23101  StopTimeDouble = double(AVPtr->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0;
23102  if(StopTimeDouble < 0.5)
23103  {
23104  StopTimeDouble = 0.5;
23105  }
23106  // at least 30 secs delay at station
23107  // can't convert a TDateTime to a float directly
23108  LaterStopTime += float(StopTimeDouble);
23109  RecoverableTime += StopTimeDouble - 0.5;
23110  if((LaterStopNumber == 1) && (TrainID > -1))
23111  {
23112  TrainVectorAtIdent(41, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23113  }
23114  AVPtr++;
23115  }
23116  else if((AVPtr->FormatType == TimeLoc) && (AVPtr->ArrivalTime != TDateTime(-1))) // must be an arrival
23117  {
23118  if((AVPtr + 1)->FormatType == TimeLoc)
23119  // must be a departure
23120  {
23121  LaterStopNumber++;
23122  StopTimeDouble = double((AVPtr + 1)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
23123  // can't convert a TDateTime to a float directly //so repeat times not required
23124  if(TrainID > -1) //exclude trains still to enter
23125  {
23126  TTrain &Train = TrainVectorAtIdent(67, TrainID);
23127  if(TTClockTime > Train.GetTrainTime(69, AVPtr->ArrivalTime)) //running late, added at v2.16.1
23128  {
23129  StopTimeDouble = double(Train.GetTrainTime(70, (AVPtr + 1)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
23130  // can't convert a TDateTime to a float directly
23131  }
23132  }
23133  if(StopTimeDouble < 0.5)
23134  {
23135  StopTimeDouble = 0.5;
23136  }
23137  // at least 30 secs delay at station
23138  LaterStopTime += float(StopTimeDouble);
23139  RecoverableTime += StopTimeDouble - 0.5;
23140  if((LaterStopNumber == 1) && (TrainID > -1))
23141  {
23142  TrainVectorAtIdent(42, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23143  }
23144  AVPtr++;
23145  AVPtr++;
23146  }
23147  else if((((AVPtr + 1)->FormatType == TimeCmdDescription) || ((AVPtr + 1)->FormatType == TimeCmdMaxSpeed)) && ((AVPtr + 2)->FormatType == TimeLoc)) //change of description or max speed then departure
23148  { //added at v2.16.1 so description changes ignored in calculating time to act //added TimeCmdMaxSpeed at v2.21.0
23149  LaterStopNumber++;
23150  StopTimeDouble = double((AVPtr + 2)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
23151  // can't convert a TDateTime to a float directly //so repeat times not required
23152  if(TrainID > -1) //exclude trains still to enter
23153  {
23154  TTrain &Train = TrainVectorAtIdent(68, TrainID);
23155  if(TTClockTime > Train.GetTrainTime(71, AVPtr->ArrivalTime)) //running late, added at v2.16.1
23156  {
23157  StopTimeDouble = double(Train.GetTrainTime(72, (AVPtr + 2)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
23158  // can't convert a TDateTime to a float directly
23159  }
23160  }
23161  if(StopTimeDouble < 0.5)
23162  {
23163  StopTimeDouble = 0.5;
23164  }
23165  // at least 30 secs delay at station
23166  LaterStopTime += float(StopTimeDouble);
23167  RecoverableTime += StopTimeDouble - 0.5;
23168  if((LaterStopNumber == 1) && (TrainID > -1))
23169  {
23170  TrainVectorAtIdent(69, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23171  }
23172  AVPtr++;
23173  AVPtr++;
23174  AVPtr++;
23175  }
23176  else // does something else at the location so no calculation needed
23177  {
23178  Utilities->CallLogPop(2086);
23179  return(-1);
23180  }
23181  }
23182  }
23183  }
23184  NextElement = Track->TrackElementAt(950, CurrentElement).Conn[CurrentExitPos];
23185  if(NextElement == -1) // reached end element, no action needed
23186  {
23187  Utilities->CallLogPop(2077);
23188  return(-1);
23189  }
23190  NextEntryPos = Track->TrackElementAt(919, CurrentElement).ConnLinkPos[CurrentExitPos];
23191  // get NextExitPos
23192  if(Track->TrackElementAt(920, NextElement).TrackType == Points)
23193  {
23194  if((NextEntryPos == 0) || (NextEntryPos == 2))
23195  // leading entry point
23196  {
23197  if(Track->TrackElementAt(921, NextElement).Attribute == 0)
23198  {
23199  NextExitPos = 1;
23200  }
23201  else
23202  {
23203  NextExitPos = 3;
23204  }
23205  }
23206  else
23207  {
23208  NextExitPos = 0; // trailing entry point
23209  }
23210  }
23211  else
23212  {
23213  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
23214  }
23215  CurrentElement = NextElement;
23216  CurrentEntryPos = NextEntryPos;
23217  CurrentExitPos = NextExitPos;
23218  CurrentExitConfig = Track->TrackElementAt(922, CurrentElement).Config[CurrentExitPos];
23219  CurrentAttribute = Track->TrackElementAt(923, CurrentElement).Attribute;
23220  CurrentElementFailed = Track->TrackElementAt(1550, CurrentElement).Failed; //added at v2.13.2
23221  }
23222  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
23223  // ok if autosig route after red signal, no action needed
23224  {
23225  int NextElement = Track->TrackElementAt(924, CurrentElement).Conn[CurrentExitPos];
23226  int NextEntryPos = Track->TrackElementAt(925, CurrentElement).ConnLinkPos[CurrentExitPos];
23227  int RouteNumber; // holder for referenced value, not used
23228  if((AllRoutes->GetRouteTypeAndNumber(31, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
23229  { //CurrentElementFailed added at v2.13.2
23230  Utilities->CallLogPop(2095);
23231  return(-1);
23232  }
23233  }
23234 
23235  if(TrackSpeedCount > 0)
23236  {
23237  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
23238  }
23239  else // shouldn't reach here but include to prevent divide by zero error
23240  {
23241  if(CurrentEntryPos > 1)
23242  {
23243  MaxAllowableSpeed = Track->TrackElementAt(1433, CurrentElement).SpeedLimit23;
23244  }
23245  else
23246  {
23247  MaxAllowableSpeed = Track->TrackElementAt(1434, CurrentElement).SpeedLimit01;
23248  }
23249  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
23250  }
23251 
23252  if(LaterStopNumber > 0)
23253  {
23254  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
23255  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
23256  }
23257  else
23258  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
23259  // average line speed/2 (for half distance accelerating and half decelerating.
23260  {
23261  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
23262  // using linear trendline for accel & decel distance at various speeds
23263  // at half braking, speed never < 60 using this
23264  }
23265  if(AvTrackSpeed > MaxAllowableSpeed)
23266  {
23267  AvTrackSpeed = MaxAllowableSpeed;
23268  }
23269  Utilities->CallLogPop(2096);
23270  return(DistanceToRedSignal);
23271 }
23272 
23273 // ---------------------------------------------------------------------------
23274 // end of TTrainController entries
23275 // ---------------------------------------------------------------------------
TTrain::LinkOccupied
bool LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber)
Added at v1.2.0: true if any part of train on specific link, false otherwise, including no link prese...
Definition: TrainUnit.cpp:9747
TAllRoutes::TrackIsInARoute
bool TrackIsInARoute(int Caller, int TrackVectorPosition, int LinkPos)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19924
TActionVectorEntry::EventTime
TDateTime EventTime
Definition: TrainUnit.h:140
TTrain::AllowedToPassRedSignal
bool AllowedToPassRedSignal
set when train has been called on, or when under signaller control and instructed to pass a red signa...
Definition: TrainUnit.h:394
JoinedByOther
@ JoinedByOther
Definition: TrainUnit.h:55
TTrainController::CheckShuttleRepeatTime
bool CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
Check that shuttle link services have consistent times, true for success.
Definition: TrainUnit.cpp:16933
TTrain::ZeroPowerNoNewShuttleFromNonRepeatMessage
bool ZeroPowerNoNewShuttleFromNonRepeatMessage
Definition: TrainUnit.h:360
TTrain::ZeroPowerNoNewServiceMessage
bool ZeroPowerNoNewServiceMessage
Definition: TrainUnit.h:359
TTrain::VOffset
int VOffset[4]
each headcode character is an 8x8 pixel graphic and must be placed within a 16x16 pixel element,...
Definition: TrainUnit.h:519
TTrainController
Handles all train and timetable activities, only one object created.
Definition: TrainUnit.h:751
TTrain::TTrain
TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, TTrainMode TrainMode, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerMaxSpeed)
Constructor, sets listed member values.
Definition: TrainUnit.cpp:73
TAllRoutes::LockedRouteVector
TLockedRouteVector LockedRouteVector
the vector that stores all the locked routes on the railway
Definition: TrackUnit.h:1746
TRailGraphics::smOrange
Graphics::TBitmap * smOrange
Definition: GraphicUnit.h:904
TActionVector
std::vector< TActionVectorEntry > TActionVector
contains all actions for a single train
Definition: TrainUnit.h:183
TUtilities::LoadFileString
AnsiString LoadFileString(std::ifstream &InFile)
loads a string value from the file
Definition: Utilities.cpp:214
TTrainController::CheckStartPositionValidity
bool CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
A timetable validation function where train starting positions are checked for validity,...
Definition: TrainUnit.cpp:16595
TTrainDataEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:218
TTrain::ChangeTrainDirection
void ChangeTrainDirection(int Caller, bool NoLogFlag)
Reverses the direction of motion of the train.
Definition: TrainUnit.cpp:6666
TActionVectorEntry::SplitDistribution
AnsiString SplitDistribution
Definition: TrainUnit.h:125
TTrain::MidEntryPos
int MidEntryPos
Definition: TrainUnit.h:382
TTrainController::RebuildOpTimeToActMultimap
void RebuildOpTimeToActMultimap(int Caller)
new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2....
Definition: TrainUnit.cpp:22584
TTrainController::PwrHigh
bool PwrHigh
Definition: TrainUnit.h:861
TTrainController::BuildContinuationTrainExpectationMultiMap
void BuildContinuationTrainExpectationMultiMap(int Caller)
populate the ContinuationTrainExpectationMultiMap during timetable loading
Definition: TrainUnit.cpp:18060
TFixedTrackPiece::GraphicPtr
Graphics::TBitmap * GraphicPtr
the track bitmap for display on the zoomed-in railway
Definition: TrackUnit.h:92
SignallerMoveForwards
@ SignallerMoveForwards
Definition: TrainUnit.h:56
TRailGraphics::CodeR
Graphics::TBitmap * CodeR
Definition: GraphicUnit.h:1017
Create
@ Create
Definition: TrainUnit.h:55
TTrainController::TContinuationEntryVecPosVector
std::vector< int > TContinuationEntryVecPosVector
ensures only one train displayed for a given continuation
Definition: TrainUnit.h:838
TAllRoutes::SetAllRearwardsSignals
void SetAllRearwardsSignals(int Caller, int Attribute, int RouteNumber, int RouteStartPosition)
Set rearwards signals from the specified route starting position.
Definition: TrackUnit.cpp:20930
Arrive
@ Arrive
Definition: TrainUnit.h:55
ChangeDirection
@ ChangeDirection
Definition: TrainUnit.h:55
TTrain::ZeroPowerNoCDTMessage
bool ZeroPowerNoCDTMessage
Definition: TrainUnit.h:358
TTrain::CallingOnFlag
bool CallingOnFlag
calling on permitted
Definition: TrainUnit.h:400
Depart
@ Depart
Definition: TrainUnit.h:55
TRailGraphics::gl89set
Graphics::TBitmap * gl89set
Definition: GraphicUnit.h:721
TTrainController::CheckHeadCodeValidity
bool CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
Returns true if the headcode complies with requirements.
Definition: TrainUnit.cpp:13103
TRailGraphics::gl88set
Graphics::TBitmap * gl88set
Definition: GraphicUnit.h:719
PerfLogForm
TPerfLogForm * PerfLogForm
Definition: PerfLogUnit.cpp:11
clBufferStopBackground
#define clBufferStopBackground
Definition: GraphicUnit.h:292
TTrain::MaxRunningSpeed
double MaxRunningSpeed
the current maximum train running speed
Definition: TrainUnit.h:446
TTrack::BarriersDownVector
TActiveLCVector BarriersDownVector
vector of LCs with barriers down
Definition: TrackUnit.h:805
ChangeMaxSpeed
@ ChangeMaxSpeed
Definition: TrainUnit.h:58
TPrefDirElement::GetXLinkPos
int GetXLinkPos() const
Returns the XLink array position.
Definition: TrackUnit.h:287
TTrack::IsLCBarrierDownAtHV
bool IsLCBarrierDownAtHV(int Caller, int HLoc, int VLoc)
True if an open (to trains) level crossing is found at H & V.
Definition: TrackUnit.cpp:7513
RestoreTimetableControl
@ RestoreTimetableControl
Definition: TrainUnit.h:56
FailCrashed
@ FailCrashed
Definition: TrainUnit.h:42
TRailGraphics::TempHeadCode
Graphics::TBitmap * TempHeadCode
Definition: GraphicUnit.h:910
TAllRoutes::AutoSigsRoute
@ AutoSigsRoute
Definition: TrackUnit.h:1675
IntermediateSequence
@ IntermediateSequence
Definition: TrainUnit.h:81
TTrack::TSigElement::Attribute
int Attribute
the signal state - red, yellow, double yellow or green
Definition: TrackUnit.h:730
TActionVectorEntry::LocationType
TTimetableLocationType LocationType
indicates where the train is when the relevant action occurs
Definition: TrainUnit.h:146
TTrack::GapFlashGreenPosition
int GapFlashGreenPosition
Definition: TrackUnit.h:787
TAllRoutes::TRouteElementPair
std::pair< int, unsigned int > TRouteElementPair
defines a specific element in a route, the first (int) value is the vector position in the AllRoutesV...
Definition: TrackUnit.h:1687
NamedNonStationLocation
@ NamedNonStationLocation
Definition: TrackUnit.h:67
FinRemHere
@ FinRemHere
Definition: TrainUnit.h:70
TTrain::HeadCodeGrPtr
Graphics::TBitmap * HeadCodeGrPtr[4]
points to the headcode segment graphics e.g. 5,A,4,7.
Definition: TrainUnit.h:536
TTrainController::TContinuationTrainExpectationEntry::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the service entry in the timetable's TrainDataVector
Definition: TrainUnit.h:799
FSHNewService
@ FSHNewService
Definition: TrainUnit.h:71
clB3G5R5
#define clB3G5R5
Definition: GraphicUnit.h:203
TTrain::CoastingBrakeRate
double CoastingBrakeRate
the train brake rate when coasting
Definition: TrainUnit.h:454
TTrain::MaxBrakeRate
double MaxBrakeRate
the maximum brake rate that the train can achieve
Definition: TrainUnit.h:450
TTimetableLocationType
TTimetableLocationType
Definition: TrainUnit.h:75
TAllRoutes::DiagonalFouledByRoute
bool DiagonalFouledByRoute(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
As above but only checks for a route (may or may not be a train present (new at v1....
Definition: TrackUnit.cpp:21691
TTrainController::CheckForDuplicateCrossReferences
bool CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
A timetable validation function where referenced services are checked for uniqueness,...
Definition: TrainUnit.cpp:15828
TTrackElement::StationEntryStopLinkPos2
int StationEntryStopLinkPos2
Definition: TrackUnit.h:153
TTrain::MidExitPos
int MidExitPos
Definition: TrainUnit.h:382
TRailGraphics::CodeD
Graphics::TBitmap * CodeD
Definition: GraphicUnit.h:1003
TTrain::TrainFailed
bool TrainFailed
Definition: TrainUnit.h:430
TUtilities::CheckFileStringZeroDelimiter
bool CheckFileStringZeroDelimiter(std::ifstream &InFile)
checks that the value is a string ('0' only accepted as the delimiter), returns true for success
Definition: Utilities.cpp:459
TTrack::TTrackVectorIterator
std::vector< TTrackElement >::iterator TTrackVectorIterator
iterator for TTrackVector
Definition: TrackUnit.h:652
TRailGraphics::Code_w
Graphics::TBitmap * Code_w
Definition: GraphicUnit.h:986
FailDerailed
@ FailDerailed
Definition: TrainUnit.h:42
TTrainController::SPADWarning
bool SPADWarning
Definition: TrainUnit.h:847
TTrain::ZeroPowerNoFrontSplitMessage
bool ZeroPowerNoFrontSplitMessage
Definition: TrainUnit.h:354
TRailGraphics::Code_s
Graphics::TBitmap * Code_s
Definition: GraphicUnit.h:982
TTrainController::GetExitLocationAndAt
AnsiString GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
Check all timetable names in ExitList, if all same return " at [name]" + AllowableExits = elements,...
Definition: TrainUnit.cpp:21872
TTrain::SaveOneSessionTrain
void SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
Data for a single train is saved to a session file.
Definition: TrainUnit.cpp:8618
TTrain::DepartureTimeSet
bool DepartureTimeSet
set when stopped at a location and the next action is departure (set in UpdateTrain when ReleaseTime ...
Definition: TrainUnit.h:402
TTrain::Plotted
bool Plotted
set when train plotted on screen
Definition: TrainUnit.h:508
TTrain::LagElement
int LagElement
Definition: TrainUnit.h:382
TTrain::RemainHere
void RemainHere(int Caller)
Sends the 'train terminated' message to the performance log and sets TimetableFinished to true.
Definition: TrainUnit.cpp:6804
TTrackElement::StationEntryStopLinkPos3
int StationEntryStopLinkPos3
Definition: TrackUnit.h:153
TTrainController::BufferAttentionWarning
bool BufferAttentionWarning
Definition: TrainUnit.h:847
TTrainController::TrainVectorAtIdent
TTrain & TrainVectorAtIdent(int Caller, int TrainID)
Return a reference to the train with ID TrainID, carries out validity checking on TrainID.
Definition: TrainUnit.cpp:11071
TPrefDirElement::GetRouteEXGraphicPtr
Graphics::TBitmap * GetRouteEXGraphicPtr()
Returns route graphic.
Definition: TrackUnit.h:323
TRailGraphics::smLightBlue
Graphics::TBitmap * smLightBlue
Definition: GraphicUnit.h:901
TTrainController::TContinuationAutoSigEntry::RouteNumber
int RouteNumber
the AllRoutesVector position of the route
Definition: TrainUnit.h:773
TTrainController::StopTTClockMessage
void StopTTClockMessage(int Caller, AnsiString Message)
sends a message to the user and stops the timetable clock while it is displayed
Definition: TrainUnit.cpp:17791
TExitInfo::TimeToExitSecs
short TimeToExitSecs
Definition: TrainUnit.h:111
TTrainController::OperatingTrainLateArr
int OperatingTrainLateArr
< all these set to 0 in constructor
Definition: TrainUnit.h:907
TRailGraphics::CodeJ
Graphics::TBitmap * CodeJ
Definition: GraphicUnit.h:1009
TUtilities::IncrementAnsiTimeOneMinute
AnsiString IncrementAnsiTimeOneMinute(AnsiString TimeVal)
takes "HH:MM" and increments it to "HH:MX", where MX == MM + 1, incrementing the hour if necessary
Definition: Utilities.cpp:850
TRailGraphics::CodeQ
Graphics::TBitmap * CodeQ
Definition: GraphicUnit.h:1016
TDisplay::GetOutputLog9
TLabel * GetOutputLog9()
Definition: DisplayUnit.h:185
TAllRoutes::RemoveRouteElement
void RemoveRouteElement(int Caller, int HLoc, int VLoc, int ELink)
Erases the route element from Route2MultiMap and from the PrefDirVector.
Definition: TrackUnit.cpp:20719
TTrainController::CrashWarning
bool CrashWarning
Definition: TrainUnit.h:847
TTrainController::CheckLocationValidity
bool CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
Returns true if the location name complies with requirements.
Definition: TrainUnit.cpp:13053
TAllRoutes::TLockedRouteClass::LastXLinkPos
int LastXLinkPos
the XLinkPos value of the last (i.e. most forward) element in the route
Definition: TrackUnit.h:1667
TTrain::PlotTrain
void PlotTrain(int Caller, TDisplay *Disp)
Plots the train on the display in normal (zoomed-in) mode.
Definition: TrainUnit.cpp:9714
TNumListIterator
TNumList::iterator TNumListIterator
Definition: TrainUnit.h:99
FailLevelCrossingCrash
@ FailLevelCrossingCrash
Definition: TrainUnit.h:44
TActionVectorEntry::DepartureTime
TDateTime DepartureTime
relevant times at which the action is timetabled, zeroed on creation so change to -1 as a marker for ...
Definition: TrainUnit.h:140
TRailGraphics::Code_f
Graphics::TBitmap * Code_f
Definition: GraphicUnit.h:969
TTrain::ArrivalMinDwellTime
double ArrivalMinDwellTime
Definition: TrainUnit.h:464
FailCreateLockedRoute
@ FailCreateLockedRoute
Definition: TrainUnit.h:43
TTrainController::TContinuationTrainExpectationEntry::IncrementalDigits
int IncrementalDigits
Repeat headcode separation.
Definition: TrainUnit.h:795
TTrainController::SaveSessionTrains
void SaveSessionTrains(int Caller, std::ofstream &SessionFile)
save trains to a session file
Definition: TrainUnit.cpp:17806
TOneCompleteFormattedTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:276
TRailGraphics::SetWebSafePalette
void SetWebSafePalette(int Caller, Graphics::TBitmap *bmp)
Definition: GraphicUnit.cpp:3473
TRailGraphics::Code_d
Graphics::TBitmap * Code_d
Definition: GraphicUnit.h:967
TTrain::JoinedBy
void JoinedBy(int Caller)
Carry out the actions needed when a train is waiting to be joined by another train.
Definition: TrainUnit.cpp:6571
FailIncorrectExit
@ FailIncorrectExit
Definition: TrainUnit.h:44
TRailGraphics::Code_t
Graphics::TBitmap * Code_t
Definition: GraphicUnit.h:983
TTrainController::GetServiceFromVector
TTrainDataEntry GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
Return the TrainDataVector entry corresponding to ServiceReference, FinishType is 0 for end of servic...
Definition: TrainUnit.cpp:21228
TRailGraphics::ChangeBackgroundColour
void ChangeBackgroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour, bool &ColourError)
Definition: GraphicUnit.cpp:3724
TTrain::GetLeadElement
int GetLeadElement()
get LeadElement - used in RouteLockingRequired in TrackUnit.cpp
Definition: TrainUnit.h:724
TimeCmd
@ TimeCmd
Definition: TrainUnit.h:70
TTrain::BackgroundPtr
Graphics::TBitmap * BackgroundPtr[4]
the existing track graphic that the train headcode segment covers up (one for each headcode segment)
Definition: TrainUnit.h:532
TUtilities::LoadFileDouble
double LoadFileDouble(std::ifstream &InFile)
loads a double value from the file (converts from a string to a double) and uses the local decimal po...
Definition: Utilities.cpp:196
TTrain::ZeroPowerDepartMessage
bool ZeroPowerDepartMessage
Definition: TrainUnit.h:363
TTrain::DistanceToStationStop
int DistanceToStationStop
calculated in UpdateTrain & used in CalcDistanceToRedSignalandStopTime to cater for trains running ea...
Definition: TrainUnit.h:482
TTrainController::EntryPos
int EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
Return the track entry link (Link[]) array position for the given train on track element at track vec...
Definition: TrainUnit.cpp:11033
SignallerLeave
@ SignallerLeave
Definition: TrainUnit.h:57
TTrain::PlotEntryPos
int PlotEntryPos[4]
the LinkPos value corresponding to the train entry link of the element where each of the 4 headcode c...
Definition: TrainUnit.h:525
NotStarted
@ NotStarted
Definition: TrainUnit.h:92
TTrack::OneNamedLocationElementAtLocation
bool OneNamedLocationElementAtLocation(int Caller, AnsiString LocationName)
True if there is at least one named location element with name 'LocationName', used in timetable inte...
Definition: TrackUnit.cpp:11811
TTrainController::SPADEvents
int SPADEvents
Definition: TrainUnit.h:901
TTrainController::LastTTTime
AnsiString LastTTTime
Stores the last time used in the timetable as an AnsiString - used for timetable analysis.
Definition: TrainUnit.h:841
TTrack::OneStationLongEnoughForSplit
bool OneStationLongEnoughForSplit(int Caller, AnsiString LocationName)
Definition: TrackUnit.cpp:11285
TTrain::ZeroPowerNoRepeatShuttleMessage
bool ZeroPowerNoRepeatShuttleMessage
Definition: TrainUnit.h:361
TTrackElement::SigAspect
enum TTrackElement::@1 SigAspect
TRailGraphics::gl90set
Graphics::TBitmap * gl90set
Definition: GraphicUnit.h:724
TTrain::ActionVectorEntryPtr
TActionVectorEntry * ActionVectorEntryPtr
points to the current position in the ActionVector (a member of the TTrainDataEntry class)
Definition: TrainUnit.h:388
TTrainDataEntry
Contains all data for a single timetable service entry.
Definition: TrainUnit.h:216
LeadMid
@ LeadMid
Definition: TrainUnit.h:309
FailMissedPass
@ FailMissedPass
Definition: TrainUnit.h:43
clSignalStopBackground
#define clSignalStopBackground
Definition: GraphicUnit.h:300
TTrainController::LateDeps
int LateDeps
Definition: TrainUnit.h:891
TAllRoutes::IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber
bool IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(int Caller, int TrackVectorPosition, int XLinkPos, TPrefDirElement &PrefDirElement, int &LockedVectorNumber)
Checks whether the preferred direction element at TrackVectorPosition with XLinkPos value is in a loc...
Definition: TrackUnit.cpp:21204
TrackUnit.h
TTrainController::CheckStartAllowable
bool CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
Called when trying to introduce a new train - checks for points in correct orientation,...
Definition: TrainUnit.cpp:16679
TTrack::ResetAllTrainIDsAndFailedPointOrigSpeedLimits
void ResetAllTrainIDsAndFailedPointOrigSpeedLimits(int Caller)
Definition: TrackUnit.cpp:7885
TTrainController::RebuildTimeToExitMultiMap
void RebuildTimeToExitMultiMap(int Caller)
new for multiplayer
Definition: TrainUnit.cpp:22755
TRailGraphics::Code_r
Graphics::TBitmap * Code_r
Definition: GraphicUnit.h:981
TTrain::SendMissedActionLogs
void SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
Missed actions (see NameInTimetableBeforeCDT above) sent to the performance log and performance file.
Definition: TrainUnit.cpp:6842
TOneRoute::RouteID
int RouteID
the ID number of the route, this is needed for session saves
Definition: TrackUnit.h:1562
TTrainController::TContinuationTrainExpectationEntry::RepeatNumber
int RepeatNumber
service RepeatNumber
Definition: TrainUnit.h:791
TRailGraphics::CodeX
Graphics::TBitmap * CodeX
Definition: GraphicUnit.h:1023
TOneTrainFormattedEntry::Time
AnsiString Time
the time of the action as a string
Definition: TrainUnit.h:263
TRailGraphics::CodeT
Graphics::TBitmap * CodeT
Definition: GraphicUnit.h:1019
TTrainDataEntry::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:226
TTrainController::LastTrainLoaded
int LastTrainLoaded
displays last train loaded from session file, used for debugging
Definition: TrainUnit.h:911
TTrainController::NotStartedTrainLateMins
float NotStartedTrainLateMins
total late minutes of trains that haven't started yet on exit operation for locations not reached yet
Definition: TrainUnit.h:868
Utilities.h
TTrainController::TContinuationAutoSigVectorIterator
TContinuationAutoSigVector::iterator TContinuationAutoSigVectorIterator
Definition: TrainUnit.h:781
TTrain::SkippedDeparture
bool SkippedDeparture
< used for terminating a service early and becoming new follow-on service
Definition: TrainUnit.h:342
TTrainController::BgndColNumber
int BgndColNumber
8 bit websafe colour number corresponding to background colour (Utilities->clTransparent)
Definition: TrainUnit.h:937
TTrain::TrainOnContinuation
bool TrainOnContinuation(int Caller)
Returns true if any part of train on a continuation - called when checking for failures,...
Definition: TrainUnit.cpp:10196
ChangeDescription
@ ChangeDescription
Definition: TrainUnit.h:58
TTrainController::CheckSessionLockedRoutes
bool CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for locked routes, true for success.
Definition: TrainUnit.cpp:17909
FailTrainEntry
@ FailTrainEntry
Definition: TrainUnit.h:41
MidLag
@ MidLag
Definition: TrainUnit.h:309
TDisplay::Update
void Update()
Repaint the screen display.
Definition: DisplayUnit.h:222
TTrainController::LocServiceTimesDepTimeSort
bool LocServiceTimesDepTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:957
TTrainController::CheckSessionTrains
bool CheckSessionTrains(int Caller, std::ifstream &InFile)
Part of the session file integrity check for train entries, true for success.
Definition: TrainUnit.cpp:17847
StartNew
@ StartNew
Definition: TrainUnit.h:70
TakeSignallerControl
@ TakeSignallerControl
Definition: TrainUnit.h:55
LeadMidLag
@ LeadMidLag
Definition: TrainUnit.h:309
TTrack::GapFlashGreen
TGraphicElement * GapFlashGreen
Definition: TrackUnit.h:809
TTrain::ZeroPowerNoRepeatShuttleOrNewServiceMessage
bool ZeroPowerNoRepeatShuttleOrNewServiceMessage
Definition: TrainUnit.h:362
TrainFailure
@ TrainFailure
Definition: TrainUnit.h:56
TTrain::MinsDelayed
float MinsDelayed
new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain
Definition: TrainUnit.h:470
TTrack::TSigElement::SigPtr
Graphics::TBitmap * SigPtr
pointer to the graphic
Definition: TrackUnit.h:732
TTrainController::TimeToExitMultiMap
TTimeToExitMultiMap TimeToExitMultiMap
Map of times to exit & exit coordinates.
Definition: TrainUnit.h:1065
TAllRoutes::FindRouteNumberFromRoute2MultiMapNoErrors
bool FindRouteNumberFromRoute2MultiMapNoErrors(int Caller, int HLoc, int VLoc, int ELink, int &RouteNumber)
If a route is present at H, V & Elink returns true with RouteNumber giving vector position in AllRout...
Definition: TrackUnit.cpp:20467
TTrainController::Last2CharactersBothDigits
bool Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
Checks the last two characters in HeadCode and returns true if both are digits.
Definition: TrainUnit.cpp:12461
TRailGraphics::Code2
Graphics::TBitmap * Code2
Definition: GraphicUnit.h:992
TTrack::GetVectorPositionsFromInactiveTrackMap
TIMPair GetVectorPositionsFromInactiveTrackMap(int Caller, int HLoc, int VLoc, bool &FoundFlag)
Similar to GetVectorPositionFromTrackMap but for inactive elements, a pair is returned because there ...
Definition: TrackUnit.cpp:5993
TTrain::BackgroundColour
TColor BackgroundColour
< Used for writing to operating image when long refs showing
Definition: TrainUnit.h:545
TTrainOperatingData::EventReported
TActionEventType EventReported
Definition: TrainUnit.h:195
TTrain
Definition: TrainUnit.h:315
TRailGraphics::Code_z
Graphics::TBitmap * Code_z
Definition: GraphicUnit.h:989
TTrainController::CheckTimeValidity
bool CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
returns true if the time complies with requirements
Definition: TrainUnit.cpp:12480
clSignallerStopped
#define clSignallerStopped
Definition: GraphicUnit.h:299
TOnePrefDir::GetFixedPrefDirElementAt
const TPrefDirElement & GetFixedPrefDirElementAt(int Caller, int At) const
Return a non-modifiable element at PrefDirVector position 'At'.
Definition: TrackUnit.cpp:12547
FailBuffersPreventingStart
@ FailBuffersPreventingStart
Definition: TrainUnit.h:44
TTrainController::LocServiceTimesArrTimeSort
bool LocServiceTimesArrTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:952
TOneTrainFormattedEntry::Action
AnsiString Action
includes location if relevant
Definition: TrainUnit.h:261
TTrainController::ContinuationTrainExpectationMultiMap
TContinuationTrainExpectationMultiMap ContinuationTrainExpectationMultiMap
Multimap for TContinuationTrainExpectationEntry objects, the access key is the expectation time.
Definition: TrainUnit.h:925
TPerfLogForm::PerformanceLog
void PerformanceLog(int Caller, AnsiString Statement)
Send Statement to the performance log on screen and to the file.
Definition: PerfLogUnit.cpp:32
TTrain::StoppedForTrainInFront
bool StoppedForTrainInFront
Definition: TrainUnit.h:514
GapJump
@ GapJump
Definition: TrackUnit.h:66
TRailGraphics::CodeK
Graphics::TBitmap * CodeK
Definition: GraphicUnit.h:1010
TTrainController::MissedStops
int MissedStops
Definition: TrainUnit.h:894
NoSequence
@ NoSequence
Definition: TrainUnit.h:81
clCallOnBackground
#define clCallOnBackground
Definition: GraphicUnit.h:293
TTrackElement::Length01
int Length01
Definition: TrackUnit.h:151
TTrackElement::SpeedLimit01
int SpeedLimit01
Definition: TrackUnit.h:151
TTrain::TerminatedMessageSent
bool TerminatedMessageSent
set when a 'train terminated' message has been logged, to prevent its being logged more than once
Definition: TrainUnit.h:426
TTrain::FinishJoin
void FinishJoin(int Caller)
Carry out the actions needed when a train is waiting to join another train.
Definition: TrainUnit.cpp:6521
TTrainController::FinishedOperation
void FinishedOperation(int Caller)
called when exiting operation mode to delete all trains and timetable data etc
Definition: TrainUnit.cpp:10657
TDisplay::DisplayOffsetV
static int DisplayOffsetV
the vertical offset of the displayed screen (as viewpoint moves down [railway moves up] this offset i...
Definition: DisplayUnit.h:79
TTrain::MidElement
int MidElement
Definition: TrainUnit.h:382
TAllRoutes::CallonVector
std::vector< TCallonEntry > CallonVector
the store of all call-on entries
Definition: TrackUnit.h:1714
TTrain::LeadElement
int LeadElement
Definition: TrainUnit.h:382
clDerailedBackground
#define clDerailedBackground
Definition: GraphicUnit.h:295
TTrainController::NotACommand
bool NotACommand(int Caller, AnsiString Text)
Checks whether a piece of text is a command and returns false if it is.
Definition: TrainUnit.cpp:12991
TTrain::RepeatNumber
int RepeatNumber
indicates which of the repeating services this train represents (0 = first service)
Definition: TrainUnit.h:376
TTrainDataEntry::ActionVector
TActionVector ActionVector
all the actions for the train
Definition: TrainUnit.h:236
TTrainController::BaseTime
TDateTime BaseTime
CurrentDateTime (i.e. real time) when operation restarts after a pause.
Definition: TrainUnit.h:754
TTrain::StartSpeed
int StartSpeed
the speed of the train when introduced into the railway (in km/h)
Definition: TrainUnit.h:380
TExitInfo::TExitInfo
TExitInfo()
Definition: TrainUnit.cpp:64
TTrainController::LoadSessionLockedRoutes
void LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
load locked routes from a session file
Definition: TrainUnit.cpp:17888
TTrainController::EarlyPasses
int EarlyPasses
Definition: TrainUnit.h:887
TTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:338
TDisplay::GetOutputLog7
TLabel * GetOutputLog7()
Definition: DisplayUnit.h:175
TTrain::TrainInFront
bool TrainInFront
Definition: TrainUnit.h:515
TTrainController::TOpTimeToActMultiMapEntry
std::pair< float, THCandTrainPosParam > TOpTimeToActMultiMapEntry
Definition: TrainUnit.h:836
FailMissedSplit
@ FailMissedSplit
Definition: TrainUnit.h:42
TTrain::EntryTime
TDateTime EntryTime
Definition: TrainUnit.h:486
TExitInfo::RepeatNumber
short RepeatNumber
Definition: TrainUnit.h:110
TTrainController::TContinuationAutoSigEntry::FirstDelay
double FirstDelay
Definition: TrainUnit.h:769
TTrain::LastSigPassedFailed
bool LastSigPassedFailed
flag used to erase route elements in an autosigs route after a failed signal
Definition: TrainUnit.h:410
TTrainController::SaveTrainDataVectorToFile
void SaveTrainDataVectorToFile(int Caller)
diagnostic function to store all train data to a file for examination, not used normally
Definition: TrainUnit.cpp:17611
TTrainController::NearTransparentColNumber
int NearTransparentColNumber
Definition: TrainUnit.h:941
TOnePrefDir::PrefDirSize
unsigned int PrefDirSize() const
Return the vector size.
Definition: TrackUnit.h:1420
TTrack::PlotSignal
void PlotSignal(int Caller, TTrackElement TrackElement, TDisplay *Disp)
Plot signals on screen according to their aspect (Attribute value)
Definition: TrackUnit.cpp:6281
End
@ End
Definition: TrackUnit.h:76
TUtilities::FixedMinRepairTime
int FixedMinRepairTime
Definition: Utilities.h:72
TTrain::OneLengthAccelDecel
bool OneLengthAccelDecel
set when a train can only move forwards one element before stopping but needs to accelerate for the f...
Definition: TrainUnit.h:416
TTrain::FloatingLabelNextString
AnsiString FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the 'Next' action.
Definition: TrainUnit.cpp:7496
TTrack::TimetabledLocationNameAllocated
bool TimetabledLocationNameAllocated(int Caller, AnsiString LocationName)
True if a non-empty LocationName found as a timetabled location name i.e. not as a continuation name.
Definition: TrackUnit.cpp:9087
StaticFeaturesDisplay
TDisplay * StaticFeaturesDisplay
The object pointer for track, text & graphics only for replacing the text of long service references.
Definition: DisplayUnit.cpp:56
TTrain::FailedTrainNoFinishJoinMessage
bool FailedTrainNoFinishJoinMessage
Definition: TrainUnit.h:356
TTrain::EnterLongServRefAsName
void EnterLongServRefAsName(int Caller, TDisplay *Disp)
This is to display the train's service ref above the train.
Definition: TrainUnit.cpp:2509
TDisplay::GetImage
TImage * GetImage()
Return a pointer to the screen image.
Definition: DisplayUnit.h:139
TTrack::InactiveTrackElementAt
TTrackElement & InactiveTrackElementAt(int Caller, int At)
A range-checked version of InactiveTrackVector.at(At)
Definition: TrackUnit.cpp:11250
ExitRailway
@ ExitRailway
Definition: TrainUnit.h:71
TPrefDirElement::GetTrackVectorPosition
unsigned int GetTrackVectorPosition() const
Returns TrackVectorPosition.
Definition: TrackUnit.h:305
TTrain::StationStopCalculated
bool StationStopCalculated
used in calculating DistanceToStationStop for trains running early before they have reached the stop ...
Definition: TrainUnit.h:422
TTrainController::LoadSessionContinuationAutoSigEntries
void LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
load ContinuationAutoSigEntries from a session file
Definition: TrainUnit.cpp:17971
FailMissedDSC
@ FailMissedDSC
Definition: TrainUnit.h:42
TTrackElement
Basic track elements as implemented in the overall railway layout.
Definition: TrackUnit.h:125
TRailGraphics::Code_n
Graphics::TBitmap * Code_n
Definition: GraphicUnit.h:977
FailMissedNewService
@ FailMissedNewService
Definition: TrainUnit.h:43
TUtilities::Format96HHMMSS
AnsiString Format96HHMMSS(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm:ss where hh runs from 00 to 95 & resets when...
Definition: Utilities.cpp:812
TRailGraphics::smSolidBgnd
Graphics::TBitmap * smSolidBgnd
Definition: GraphicUnit.h:1027
clTRSBackground
#define clTRSBackground
Definition: GraphicUnit.h:304
TTrainController::OtherMissedEvents
int OtherMissedEvents
Definition: TrainUnit.h:899
TTrainController::EarlyExits
int EarlyExits
Definition: TrainUnit.h:888
TTrain::ExitSpeedHalf
double ExitSpeedHalf
speed when half way into the next element
Definition: TrainUnit.h:440
SignalPost
@ SignalPost
Definition: TrackUnit.h:66
TRailGraphics::Code_h
Graphics::TBitmap * Code_h
Definition: GraphicUnit.h:971
TTrain::LastActionTime
TDateTime LastActionTime
time of the last timetabled event, used to ensure at least a 30 second delay before the next action
Definition: TrainUnit.h:491
TDisplay::GetOutputLog1
TLabel * GetOutputLog1()
Return pointers to warning message logs (appear above the railway display during operation)
Definition: DisplayUnit.h:144
TAllRoutes::TRoute2MultiMapIterator
TRoute2MultiMap::iterator TRoute2MultiMapIterator
Definition: TrackUnit.h:1691
TTrain::TrainFailurePending
bool TrainFailurePending
set when failure due & takes effect when all PlotElements properly set, added at v2....
Definition: TrainUnit.h:366
FailMissedTerminate
@ FailMissedTerminate
Definition: TrainUnit.h:43
TTrainController::SecondPassActions
bool SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag)
Carry out further detailed timetable consistency checks, return true for success.
Definition: TrainUnit.cpp:13740
TRailGraphics::Code9
Graphics::TBitmap * Code9
Definition: GraphicUnit.h:999
TTrainDataEntry::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:218
FailTrainInFront
@ FailTrainInFront
Definition: TrainUnit.h:45
TTrain::TrainHasFailed
void TrainHasFailed(int Caller)
Called when there is a random train failure.
Definition: TrainUnit.cpp:6143
TTrain::LongServRefWorkingBitmap
Graphics::TBitmap * LongServRefWorkingBitmap
< Stores the long service ref name for > 4 chars
Definition: TrainUnit.h:540
TUtilities::MinorDelayFactor
float MinorDelayFactor
Definition: Utilities.h:53
TTrain::LongServRefEnteredFlag
bool LongServRefEnteredFlag
defines whether service ref plotted or not
Definition: TrainUnit.h:498
clNormalBackground
#define clNormalBackground
Definition: GraphicUnit.h:298
TTrainController::CheckNonRepeatingShuttleLinksAndSetData
bool CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where cross references are checked for validity for non-repeating shu...
Definition: TrainUnit.cpp:16956
TTrainController::CheckFourthValidityForSplit
bool CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages)
Checks fourth segment in timetable for train splits - percentage mass then '-' then percentage power ...
Definition: TrainUnit.cpp:13008
TActionVectorEntry::NonRepeatingShuttleLinkEntryPtr
TTrainDataEntry * NonRepeatingShuttleLinkEntryPtr
pointer used by shuttles for the non-shuttle train links, in & out, the corresponding non-shuttle lin...
Definition: TrainUnit.h:154
TTrainController::SPADRisks
int SPADRisks
Definition: TrainUnit.h:902
TTrainController::TLocServiceTimesVector
std::vector< TLocServiceTimes > TLocServiceTimesVector
Definition: TrainUnit.h:822
TTrainController::LocServiceTimesAtLocTimeSort
bool LocServiceTimesAtLocTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:961
TTrain::EntrySpeed
double EntrySpeed
speed at which the train enters the next element
Definition: TrainUnit.h:438
TConfiguration
TConfiguration
< describes the type of track link. 'End' is used for both buffer stop and continuation entry/exit po...
Definition: TrackUnit.h:75
TTrain::TrainInFrontMessage
bool TrainInFrontMessage
flags to indicate whether the respective message has been sent
Definition: TrainUnit.h:364
TTrain::TrainGone
bool TrainGone
set when train has left the railway, so it can be removed from the display at the next clock tick
Definition: TrainUnit.h:510
TTrackType
TTrackType
< describes the type of track element
Definition: TrackUnit.h:65
TTrainController::OnTimePasses
int OnTimePasses
Definition: TrainUnit.h:897
TTrainController::SigSHigh
bool SigSHigh
Definition: TrainUnit.h:861
TRailGraphics::smCaramel
Graphics::TBitmap * smCaramel
Definition: GraphicUnit.h:898
TGraphicElement::PlotOriginal
void PlotOriginal(int Caller, TDisplay *Disp)
Plot the original graphic on screen.
Definition: TrackUnit.cpp:1928
TExitInfo
Definition: TrainUnit.h:107
clBufferAttentionNeeded
#define clBufferAttentionNeeded
Definition: GraphicUnit.h:291
TUtilities::ModerateDelayCutoff
float ModerateDelayCutoff
Definition: Utilities.h:51
TTrain::StoppedAfterSPAD
bool StoppedAfterSPAD
Definition: TrainUnit.h:514
FailSPAD
@ FailSPAD
Definition: TrainUnit.h:41
TTrainController::OnTimeArrivals
int OnTimeArrivals
Definition: TrainUnit.h:895
clTrainFailedBackground
#define clTrainFailedBackground
Definition: GraphicUnit.h:305
TRailGraphics::Code_x
Graphics::TBitmap * Code_x
Definition: GraphicUnit.h:987
clFrontCodeSignaller
#define clFrontCodeSignaller
Definition: GraphicUnit.h:296
TimeCmdHeadCode
@ TimeCmdHeadCode
Definition: TrainUnit.h:70
Utilities
TUtilities * Utilities
Definition: Utilities.cpp:47
TTrainController::LongServRefFontColNumber
int LongServRefFontColNumber
the websafe colour number for long serv ref names
Definition: TrainUnit.h:939
TFixedTrackPiece::SmallGraphicPtr
Graphics::TBitmap * SmallGraphicPtr
the track bitmap for display on the zoomed-out railway
Definition: TrackUnit.h:94
TActionVectorEntry::ArrivalTime
TDateTime ArrivalTime
Definition: TrainUnit.h:140
TTrain::StoppedAtBuffers
bool StoppedAtBuffers
Definition: TrainUnit.h:514
TTrainController::CreateTTAnalysisFile
bool CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked, bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
Generate a timetable analysis file in the 'Formatted Timetables' folder, return false if failed for a...
Definition: TrainUnit.cpp:18792
TRailGraphics::Code_b
Graphics::TBitmap * Code_b
Definition: GraphicUnit.h:965
TTrain::Mass
int Mass
in kg
Definition: TrainUnit.h:480
TRailGraphics::Code0
Graphics::TBitmap * Code0
Definition: GraphicUnit.h:990
TTrainController::TContinuationTrainExpectationEntry::IncrementalMinutes
int IncrementalMinutes
Repeat separation in minutes.
Definition: TrainUnit.h:793
TTrain::LeadExitPos
int LeadExitPos
Definition: TrainUnit.h:382
TTrain::FrontElementLength
int FrontElementLength
values associated with the element immediately in front of the train (speed in km/h,...
Definition: TrainUnit.h:478
TTimetableShuttleLinkType
TTimetableShuttleLinkType
Definition: TrainUnit.h:85
TRailGraphics::CodeP
Graphics::TBitmap * CodeP
Definition: GraphicUnit.h:1015
Pass
@ Pass
Definition: TrainUnit.h:57
TTrain::SkipPtrValue
int SkipPtrValue
stores the pointer increment from first action in ActionVector for skipped actions when a departure i...
Definition: TrainUnit.h:390
TNumList
std::list< int > TNumList
a list of valid train exit TrackVector positions for 'Fer' entries
Definition: TrainUnit.h:95
TTrainController::DerailWarning
bool DerailWarning
Definition: TrainUnit.h:847
TTrainController::TContinuationTrainExpectationMultiMapPair
std::pair< TDateTime, TContinuationTrainExpectationEntry > TContinuationTrainExpectationMultiMapPair
a single multimap entry
Definition: TrainUnit.h:807
TTrain::ImageLongServRefBitmap
Graphics::TBitmap * ImageLongServRefBitmap
< General purpose storage for long serv ref display
Definition: TrainUnit.h:542
RouteForceCancelled
@ RouteForceCancelled
Definition: TrainUnit.h:45
EnRoute
@ EnRoute
Definition: TrainUnit.h:76
TTrain::IsTrainIDOnBridgeTrackPos23
bool IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 2 & 3.
Definition: TrainUnit.cpp:3654
TTrack::DiagonalFouledByTrain
bool DiagonalFouledByTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber, int &TrainID)
As DiagonalFouledByRouteOrTrain (in TAllRoutes) but only checks for a train (may or may not be a rout...
Definition: TrackUnit.cpp:12259
TTrainController::TotLateDepMins
float TotLateDepMins
Definition: TrainUnit.h:879
TDisplay::ZoomOutFlag
bool ZoomOutFlag
true when zoomed-out
Definition: DisplayUnit.h:70
TRailGraphics::CodeC
Graphics::TBitmap * CodeC
Definition: GraphicUnit.h:1002
TTrain::PlotElement
int PlotElement[4]
the TrackVectorPosition of the element where each of the 4 headcode characters is plotted (need to be...
Definition: TrainUnit.h:523
TTrainController::TServiceCallingLocsList
std::list< AnsiString > TServiceCallingLocsList
Used in determining train directions in timetable conflict analysis.
Definition: TrainUnit.h:825
TTrainController::TrainVector
TTrainVector TrainVector
vector containing all trains currently in the railway
Definition: TrainUnit.h:933
TUtilities::ModerateDelayFactor
float ModerateDelayFactor
Definition: Utilities.h:54
TUtilities::MajorDelayCutoff
float MajorDelayCutoff
Definition: Utilities.h:52
TRailGraphics::Code_l
Graphics::TBitmap * Code_l
Definition: GraphicUnit.h:975
TTrainController::TContinuationAutoSigEntry
< TTClockTime when last session saved - to prevent display of warning message on exit session if < 5 ...
Definition: TrainUnit.h:767
TUtilities::CallLogPop
void CallLogPop(int Caller)
pops the last entry off the call stack, throws an error if called when empty
Definition: Utilities.cpp:50
TTrain::IncrementalMinutes
int IncrementalMinutes
the number of minutes to increment by in repeat entries
Definition: TrainUnit.h:370
TTrain::MaximumSpeedLimit
static const int MaximumSpeedLimit
Definition: TrainUnit.h:330
TRailGraphics::CodeI
Graphics::TBitmap * CodeI
Definition: GraphicUnit.h:1008
TTrainController::TotEarlyArrMins
float TotEarlyArrMins
values for performance file summary
Definition: TrainUnit.h:874
TTrain::WriteTrainToImage
void WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TrainController::WriteTrainsToImage (called by TInterface::SaveOperatingImage1Click) to add...
Definition: TrainUnit.cpp:9727
TRailGraphics::CodeL
Graphics::TBitmap * CodeL
Definition: GraphicUnit.h:1011
TTrainDataEntry::MaxBrakeRate
double MaxBrakeRate
< true if a description is given for the train, if only headcode given for a follow-on service then f...
Definition: TrainUnit.h:222
FNSNonRepeatToShuttle
@ FNSNonRepeatToShuttle
Definition: TrainUnit.h:70
TDisplay::PlotOutput
void PlotOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot the graphic at screen position HPos & VPos.
Definition: DisplayUnit.cpp:87
TUtilities::SaveFileBool
void SaveFileBool(std::ofstream &OutFile, bool SaveBool)
gives a delay od Msec value;
Definition: Utilities.cpp:108
TTrainDataEntry::SignallerSpeed
int SignallerSpeed
in km/h for use when under signaller control
Definition: TrainUnit.h:232
TTrain::AbleToMove
bool AbleToMove(int Caller)
Indicates that a train is not prevented from moving - used to allow appropriate popup menu options wh...
Definition: TrainUnit.cpp:7345
TTrainController::MTBFHours
double MTBFHours
<Message flags in TT checks to stop being given twice
Definition: TrainUnit.h:866
TTrack::TInfrastructureFailureEntry::TVPos
int TVPos
Definition: TrackUnit.h:715
TOneRoute::ForceCancelRoute
void ForceCancelRoute(int Caller)
Cancel a route immediately if a train occupies it when travelling in the wrong direction (or occupies...
Definition: TrackUnit.cpp:19505
TTrain::TrainMode
TTrainMode TrainMode
mode of operation - either Timetable (running under timetable control) or Signaller (running under si...
Definition: TrainUnit.h:495
TTrainController::CheckNonRepeatingShuttleLinkTime
bool CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ReverseEventTime, TDateTime ForwardEventTime, int RepeatMins, int RepeatNumber)
The forward train is the finish shuttle entry 'Fns-sh', the reverse (new non-repeating service) time ...
Definition: TrainUnit.cpp:17225
FailNoPowerUnableToDepart
@ FailNoPowerUnableToDepart
Definition: TrainUnit.h:45
TActionVectorEntry::NewMaxSpeed
AnsiString NewMaxSpeed
Definition: TrainUnit.h:125
TUtilities::Format96HHMM
AnsiString Format96HHMM(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm where hh runs from 00 to 95 & resets when it...
Definition: Utilities.cpp:831
TTrain::CallOnMaxSpeed
static const int CallOnMaxSpeed
km/h
Definition: TrainUnit.h:322
TTrain::CheckAndCancelRouteForWrongEndEntry
void CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
Checks whether Element and EntryPos (where train is about to enter) is on an existing route (or cross...
Definition: TrainUnit.cpp:3862
TRailGraphics::CodeW
Graphics::TBitmap * CodeW
Definition: GraphicUnit.h:1022
TTrain::ReleaseTime
TDateTime ReleaseTime
Definition: TrainUnit.h:488
TTrain::GetTrainHeadCode
AnsiString GetTrainHeadCode(int Caller)
Returns the train headcode, taking account of the RepeatNumber.
Definition: TrainUnit.cpp:5670
TTrain::LowEntryValue
bool LowEntryValue(int EntryLink) const
Returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i....
Definition: TrainUnit.cpp:3175
RearSplit
@ RearSplit
Definition: TrainUnit.h:55
TActionVectorEntry::FrontStartOrRepeatDigits
int FrontStartOrRepeatDigits
dual-purpose variables used for the TrackVectorPositions of the rear and front train starting element...
Definition: TrainUnit.h:138
SignallerControlStop
@ SignallerControlStop
Definition: TrainUnit.h:57
TTrack::TrackVector
TTrackVector TrackVector
Definition: TrackUnit.h:829
TTrain::HasTrainGone
bool HasTrainGone()
Check whether the train has left the railway, so that it can be removed from the display at the next ...
Definition: TrainUnit.h:708
TRailGraphics::Code_i
Graphics::TBitmap * Code_i
Definition: GraphicUnit.h:972
TTrainController::ExcessLCDownMins
float ExcessLCDownMins
total excess time in minutes over the 3 minutes barriers down allowance for level crossings
Definition: TrainUnit.h:872
TRailGraphics::Code5
Graphics::TBitmap * Code5
Definition: GraphicUnit.h:995
TTrackElement::CallingOnSet
bool CallingOnSet
Used for for signals only when a train is being called on - used to plot the position lights.
Definition: TrackUnit.h:135
TUtilities::SaveFileInt
void SaveFileInt(std::ofstream &OutFile, int SaveInt)
stores the int value to the file, then a CR
Definition: Utilities.cpp:121
TTrain::IsLongServRefDisplayRequired
void IsLongServRefDisplayRequired(int Caller, TDisplay *Disp)
function that checks if long serv ref display needed and if so removes earlier display if plotted the...
Definition: TrainUnit.cpp:2492
TrainController
TTrainController * TrainController
the object pointer, one object only - created in InterfaceUnit
Definition: TrainUnit.cpp:56
TUtilities::FailureMode
TFailureMode FailureMode
specifies whether no failures or minor, moderate or major random failures are to be applied (added at...
Definition: Utilities.h:125
TTrain::BufferAtExit
bool BufferAtExit(int Caller, int Element, int Exitpos) const
True if Element is a buffer and Exitpos is the buffer end.
Definition: TrainUnit.cpp:3586
TTrain::IsTrainTerminating
bool IsTrainTerminating(int Caller)
True if train service terminates at its current location.
Definition: TrainUnit.cpp:7317
TTrainController::SecondPassMessage
void SecondPassMessage(bool GiveMessages, AnsiString Message)
Give a user message during timetable integrity checking if GiveMessages is true, ignore if false.
Definition: TrainUnit.cpp:17323
SignallerStepForward
@ SignallerStepForward
Definition: TrainUnit.h:57
TTrack::GetHLocMin
int GetHLocMin()
Definition: TrackUnit.h:895
TDisplay::GetOutputLog5
TLabel * GetOutputLog5()
Definition: DisplayUnit.h:165
TTrainController::LateExits
int LateExits
Definition: TrainUnit.h:893
TTrainController::OpTimeToActUpdateCounter
unsigned int OpTimeToActUpdateCounter
<List of all ServiceRefs that have two or more same locations without a cdt between - loaded during S...
Definition: TrainUnit.h:917
TTrain::BeingCalledOn
bool BeingCalledOn
in course of being called on to a station
Definition: TrainUnit.h:396
TUtilities::CheckFileInt
bool CheckFileInt(std::ifstream &InFile, int Lowest, int Highest)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success
Definition: Utilities.cpp:262
TRailGraphics::ChangeForegroundColour2
void ChangeForegroundColour2(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
New function to do the same as the above but with fewer pixel changes - for use in LoadSession to avo...
Definition: GraphicUnit.cpp:3674
TRailGraphics::Code_e
Graphics::TBitmap * Code_e
Definition: GraphicUnit.h:968
TTrain::CalcTimeToAct
float CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair)
new v2.2.0 for operator action panel. Calculates the time left for operator action to avoid unnecessa...
Definition: TrainUnit.cpp:9795
TUtilities::SignalChangeEventsPerFailure
int SignalChangeEventsPerFailure
number of signal changes between failures - reciprocal of failure probability per change
Definition: Utilities.h:101
TTrain::HoldAtLocationInTTMode
bool HoldAtLocationInTTMode
true if actions are needed before train departs
Definition: TrainUnit.h:346
TTrack::TInfrastructureFailureEntry
Definition: TrackUnit.h:714
TTrain::PlotTrainInZoomOutMode
void PlotTrainInZoomOutMode(int Caller, bool Flash)
Plots the train on screen in zoomed-out mode, state of 'Flash' determines whether the flashing trains...
Definition: TrainUnit.cpp:9536
TTrain::LoadOneSessionTrain
void LoadOneSessionTrain(int Caller, std::ifstream &InFile)
Create one train with relevant member values from the sesion file.
Definition: TrainUnit.cpp:8815
TTrain::PlotBackgroundGraphic
void PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
Replot the graphic pointed to by BackgroundPtr (see above) after a train has passed.
Definition: TrainUnit.cpp:3578
TTrainController::TotLateArrMins
float TotLateArrMins
Definition: TrainUnit.h:878
TTrain::HOffset
int HOffset[4]
Definition: TrainUnit.h:519
FailMissedArrival
@ FailMissedArrival
Definition: TrainUnit.h:42
TTrainController::TrainDataVectorCopy
TTrainDataVector TrainDataVectorCopy
vector containing the internal timetable, the copy is used for conflict analysis only
Definition: TrainUnit.h:931
TTrain::UpdateCounter
unsigned int UpdateCounter
used in train splitting operations to prevent too frequent checks for a location being long enough fo...
Definition: TrainUnit.h:484
NewService
@ NewService
Definition: TrainUnit.h:55
FailEnterLockedRoute
@ FailEnterLockedRoute
Definition: TrainUnit.h:43
TTrainController::PlotAllTrainsInZoomOutMode
void PlotAllTrainsInZoomOutMode(int Caller, bool Flash)
Plots all trains on screen in zoomed-out mode, state of 'Flash' determines whether the flashing train...
Definition: TrainUnit.cpp:18124
TOneRoute
A descendent of TOnePrefDir used for routes. Used during contruction of a route (ConstructRoute) and ...
Definition: TrackUnit.h:1522
TTrain::SignallerStopped
bool SignallerStopped
Definition: TrainUnit.h:514
TTrain::BufferZoomOutFlashRequired
bool BufferZoomOutFlashRequired
set when train is at buffers and is to flash in zoomout mode (i.e. when reaches buffers unexpectedly ...
Definition: TrainUnit.h:398
TAllRoutes::TRouteType
TRouteType
Definition: TrackUnit.h:1674
TTrain::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the current position in the timetable's TrainDataVector
Definition: TrainUnit.h:386
TTrain::LongServRefNameBitmap
Graphics::TBitmap * LongServRefNameBitmap
Definition: TrainUnit.h:538
TTrainDataEntry::StartSpeed
int StartSpeed
in km/h
Definition: TrainUnit.h:234
TTrainDataEntry::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:230
TTrainController::Derailments
int Derailments
Definition: TrainUnit.h:885
clStationStopBackground
#define clStationStopBackground
Definition: GraphicUnit.h:302
TUtilities::CumulativeDelayedRandMinsAllTrains
int CumulativeDelayedRandMinsAllTrains
the running total of all random delays including knock-on delays for all trains, used to reduce total...
Definition: Utilities.h:107
TDisplay::GetOutputLog6
TLabel * GetOutputLog6()
Definition: DisplayUnit.h:170
ShuttleLink
@ ShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::CodeM
Graphics::TBitmap * CodeM
Definition: GraphicUnit.h:1012
Crossover
@ Crossover
Definition: TrackUnit.h:66
TTrain::ClearToNextSignal
bool ClearToNextSignal(int Caller)
Checks forward from train LeadElement, following leading point attributes but ignoring trailing point...
Definition: TrainUnit.cpp:5270
AtLocation
@ AtLocation
Definition: TrainUnit.h:76
Signal
@ Signal
Definition: TrackUnit.h:76
TRailGraphics::Code_k
Graphics::TBitmap * Code_k
Definition: GraphicUnit.h:974
TRailGraphics::CodeF
Graphics::TBitmap * CodeF
Definition: GraphicUnit.h:1005
TTrack::ContinuationNameMap
std::map< AnsiString, char > ContinuationNameMap
map of all continuation names, char is a dummy
Definition: TrackUnit.h:797
TAllRoutes::GetRouteTypeAndNumber
TRouteType GetRouteTypeAndNumber(int Caller, int TrackVectorPosition, int LinkPos, int &RouteNumber)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:20161
TTrainController::TrainDataVector
TTrainDataVector TrainDataVector
Definition: TrainUnit.h:931
TTrainController::ConsolidateSARNTAtLoc
AnsiString ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for AtLoc listing ...
Definition: TrainUnit.cpp:21507
TAllRoutes::NotAutoSigsRoute
@ NotAutoSigsRoute
Definition: TrackUnit.h:1675
TTrain::ExitTimeHalf
TDateTime ExitTimeHalf
Definition: TrainUnit.h:486
Exited
@ Exited
Definition: TrainUnit.h:92
TTrack::TrackElementAt
TTrackElement & TrackElementAt(int Caller, int At)
A range-checked version of TrackVector.at(At)
Definition: TrackUnit.cpp:11236
TTrain::ActionsSkippedFlag
bool ActionsSkippedFlag
prevents any further skipping until after the next departure
Definition: TrainUnit.h:344
TTrainController::LoadSessionTrains
void LoadSessionTrains(int Caller, std::ifstream &SessionFile)
load trains from a session file
Definition: TrainUnit.cpp:17822
TTrain::CheckOneSessionTrain
static bool CheckOneSessionTrain(std::ifstream &InFile)
Carries out an integrity check for the train section of a session file, if fails a message is given a...
Definition: TrainUnit.cpp:9102
TAllRoutes::GetRouteTypeAndGraphics
TRouteType GetRouteTypeAndGraphics(int Caller, int TrackVectorPosition, int LinkPos, Graphics::TBitmap *&EXGraphicPtr, Graphics::TBitmap *&EntryDirectionGraphicPtr)
Examines Route2MultiMap for the element at TrackVectorPosition with LinkPos (can be entry or exit).
Definition: TrackUnit.cpp:19987
TRailGraphics::CodeY
Graphics::TBitmap * CodeY
Definition: GraphicUnit.h:1024
TTrain::SetOneGraphicCode
Graphics::TBitmap * SetOneGraphicCode(char CodeChar)
Return a pointer to the graphic corresponding to the character 'CodeVhar'.
Definition: TrainUnit.cpp:2793
TTrain::DerailPending
bool DerailPending
Definition: TrainUnit.h:514
TTrainMode
TTrainMode
indicates train operating mode, 'None' for not in use
Definition: TrainUnit.h:63
TAllRoutes::TCallonEntry
Used to store relevant values when a call-on found, ready for plotting an unrestricted route.
Definition: TrackUnit.h:1696
TRailGraphics::Code_a
Graphics::TBitmap * Code_a
Definition: GraphicUnit.h:964
TTrain::StoppedAtLocation
bool StoppedAtLocation
Definition: TrainUnit.h:514
TTrainController::TContinuationTrainExpectationMultiMapIterator
TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator
iterator for the multimap
Definition: TrainUnit.h:805
TTrain::MaximumMassLimit
static const int MaximumMassLimit
kg (i.e. 10,000 tonnes)
Definition: TrainUnit.h:324
TTrainController::StopTTClockFlag
bool StopTTClockFlag
when true the timetable clock is stopped, used for messages display and train popup menu display etc
Definition: TrainUnit.h:849
FrontSplit
@ FrontSplit
Definition: TrainUnit.h:55
TTrain::MaxExitSpeed
double MaxExitSpeed
the maximum speed that the train can exit the next element
Definition: TrainUnit.h:448
TTrainController::BFHigh
bool BFHigh
Definition: TrainUnit.h:861
TRailGraphics::Code4
Graphics::TBitmap * Code4
Definition: GraphicUnit.h:994
TRailGraphics::Code_g
Graphics::TBitmap * Code_g
Definition: GraphicUnit.h:970
TTrain::LagEntryPos
int LagEntryPos
Definition: TrainUnit.h:382
SNTShuttle
@ SNTShuttle
Definition: TrainUnit.h:70
TTrain::NewDelay
double NewDelay
an additional random delay at a location (added at v2.13.0)
Definition: TrainUnit.h:460
TRailGraphics::CodeG
Graphics::TBitmap * CodeG
Definition: GraphicUnit.h:1006
Leave
@ Leave
Definition: TrainUnit.h:55
TTrainController::UnexpectedExits
int UnexpectedExits
Definition: TrainUnit.h:904
TTrainController::TLocServiceTimes::AtLocTime
AnsiString AtLocTime
Definition: TrainUnit.h:817
TTrainController::ServiceReference
AnsiString ServiceReference
String used to display the offending service in timetable error messages.
Definition: TrainUnit.h:845
TTrain::FirstLaterStopRecoverableTime
float FirstLaterStopRecoverableTime
this used to deduct from RecoverableTime when arrive at a location for OperatorActionpanel (OperatorA...
Definition: TrainUnit.h:476
TTrack::FailedSignalsVector
TFailedElementVector FailedSignalsVector
Definition: TrackUnit.h:795
TTrain::LeavingUnderSigControlAtContinuation
bool LeavingUnderSigControlAtContinuation
set when the train has reached an exit continuation when under signaller control, used to prevent the...
Definition: TrainUnit.h:412
TTrain::LongServRefTextH
int LongServRefTextH
stores the HPos position of the service ref train name
Definition: TrainUnit.h:500
TTrain::PlotTrainGraphic
void PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
Plot the train's headcode character corresponding to ArrayNumber.
Definition: TrainUnit.cpp:3559
TRailGraphics::Code_j
Graphics::TBitmap * Code_j
Definition: GraphicUnit.h:973
FNil
@ FNil
Definition: Utilities.h:43
NoFormat
@ NoFormat
Definition: TrainUnit.h:70
TTrainController::TwoOrMoreLocationsWarningGiven
bool TwoOrMoreLocationsWarningGiven
new at v2.6.0 to allow loops
Definition: TrainUnit.h:857
FailBufferCrash
@ FailBufferCrash
Definition: TrainUnit.h:44
WaitingForFJO
@ WaitingForFJO
Definition: TrainUnit.h:44
FailMissedExitRailway
@ FailMissedExitRailway
Definition: TrainUnit.h:43
TTrain::NonDefaultMinDwellTimeFlag
bool NonDefaultMinDwellTimeFlag
set when an explicit min dwell time is set
Definition: TrainUnit.h:414
TTrainController::ControllerGetNewServiceDepartureInfo
AnsiString ControllerGetNewServiceDepartureInfo(int Caller, TActionVectorIterator Ptr, int RptNum, TTrainDataEntry *TDEPtr, TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
Similar to TTrain::GetNewServiceDepartureInfo for use in ContinuationEntryFloatingTTString.
Definition: TrainUnit.cpp:11316
TimeTimeLoc
@ TimeTimeLoc
Definition: TrainUnit.h:70
TTrain::GetOffsetValues
void GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
Sets HOffset & VOffset (see above) for a single headcode character depending on the Link value.
Definition: TrainUnit.cpp:3104
TTrain::TimeToExit
float TimeToExit
in minutes: new for multiplayer, -1 = > 60 mins
Definition: TrainUnit.h:474
TTrain::Derailed
bool Derailed
Definition: TrainUnit.h:514
TRailGraphics::ChangeSpecificColour
void ChangeSpecificColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor ColourToBeChanged, TColor NewColour)
Definition: GraphicUnit.cpp:3703
Enter
@ Enter
Definition: TrainUnit.h:55
TRailGraphics::bm93set
Graphics::TBitmap * bm93set
Definition: GraphicUnit.h:518
TTrack::GetTrackVectorPositionFromString
int GetTrackVectorPositionFromString(int Caller, AnsiString String, bool GiveMessages)
Takes the ElementID value (an AnsiString) (e.g. "8-13", "N43-N127", etc) and returns the correspondin...
Definition: TrackUnit.cpp:8100
TRailGraphics::Code_q
Graphics::TBitmap * Code_q
Definition: GraphicUnit.h:980
TTrainController::SaveSessionLockedRoutes
void SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
save locked routes to a session file
Definition: TrainUnit.cpp:17871
TTrainController::LongServRefFont
TFont * LongServRefFont
the font used for long serv ref names
Definition: TrainUnit.h:935
TTrain::OriginalPowerAtRail
double OriginalPowerAtRail
new at v2.4.0 to store value before a failure so it can be restored from here when repaired
Definition: TrainUnit.h:468
TTrainFormattedInformation
Contains all information for a single timetable entry for use in the formatted timetable.
Definition: TrainUnit.h:287
TTrainController::IsSNTEntryLocated
bool IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName)
New trains introduced with 'Snt' may be at a timetabled location or elsewhere. This function checks a...
Definition: TrainUnit.cpp:16503
TActionVectorEntry::NewDescription
AnsiString NewDescription
Definition: TrainUnit.h:125
TTrainController::TContinuationTrainExpectationEntry::VectorPosition
int VectorPosition
TrackVectorPosition for the continuation element.
Definition: TrainUnit.h:797
TTrain::NewTrainService
void NewTrainService(int Caller, bool NoLogFlag)
Carry out the actions needed when a train forms a new service (code Fns)
Definition: TrainUnit.cpp:6749
TTrainController::TTrainController
TTrainController()
Constructor.
Definition: TrainUnit.cpp:10233
Timetable
@ Timetable
Definition: TrainUnit.h:64
TUtilities::SaveFileDouble
void SaveFileDouble(std::ofstream &OutFile, double SaveDouble)
converts the double value to a string (if double stored directly it is truncated to 6 digits) then st...
Definition: Utilities.cpp:127
TTrainController::GetRepeatHeadCode
AnsiString GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
Return the service headcode for the repeat service.
Definition: TrainUnit.cpp:16888
StartSequence
@ StartSequence
Definition: TrainUnit.h:81
TTrain::AbleToMoveButForSignal
bool AbleToMoveButForSignal(int Caller)
Indicates that a train is only prevented from moving by a signal - used to allow appropriate popup me...
Definition: TrainUnit.cpp:7404
TTrainController::OpActionPanelVisible
bool OpActionPanelVisible
new v2.2.0 flag to prevent time to act functions when not visible
Definition: TrainUnit.h:855
TTrack::GetAnyElementOppositeLinkPos
int GetAnyElementOppositeLinkPos(int Caller, int TrackVectorPosition, int LinkPos, bool &Derail)
Return the opposite link position for the element at TrackVectorPosition with link position LinkPos,...
Definition: TrackUnit.cpp:12110
TTrack::GapFlashRedPosition
int GapFlashRedPosition
TrackVectorPosition of the gap element that is flashing green or red.
Definition: TrackUnit.h:787
TActionVectorEntry::NumberOfRepeats
int NumberOfRepeats
< minimum waiting time at a location, default 30s, float because float needed for TDateTime
Definition: TrainUnit.h:136
TAllRoutes::GetFixedRouteAt
const TOneRoute & GetFixedRouteAt(int Caller, int At) const
Returns a constant reference to the route at AllRoutesVector position 'At', after performing range ch...
Definition: TrackUnit.cpp:19833
TUtilities::clTransparent
TColor clTransparent
the display background colour, can be white, black or dark blue
Definition: Utilities.h:121
TTrainController::SignalStopWarning
bool SignalStopWarning
Definition: TrainUnit.h:847
TPrefDirElement::GetELinkPos
int GetELinkPos() const
Returns the ELink array position.
Definition: TrackUnit.h:275
TTrainController::CalcOperatingAndNotStartedTrainLateness
void CalcOperatingAndNotStartedTrainLateness(int Caller)
calculates additional lateness values for trains that haven't reached their destinations yet
Definition: TrainUnit.cpp:22476
ShuttleLinkTypeForRepeatEntry
@ ShuttleLinkTypeForRepeatEntry
Definition: TrainUnit.h:86
TTrain::SignallerStopBrakeRate
double SignallerStopBrakeRate
the train brake rate when stopping under signaller control
Definition: TrainUnit.h:456
TTrainController::TLocServiceTimes::Location
AnsiString Location
Definition: TrainUnit.h:815
TOneCompleteFormattedTrain
A single train with its headcode + list of actions for use in the formatted timetable.
Definition: TrainUnit.h:274
TTrainController::SignallerTrainRemovedOnAutoSigsRoute
bool SignallerTrainRemovedOnAutoSigsRoute
true if train was on an AutoSigsRoute when removed by the signaller
Definition: TrainUnit.h:853
TTrack::InactiveTrackVector
TTrackVector InactiveTrackVector
Definition: TrackUnit.h:829
TTrain::LogAction
void LogAction(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution, TDateTime TimetableNonRepeatTime, bool Warning)
Send a message to the performance log and performance file, and if the message is flagged as a warnin...
Definition: TrainUnit.cpp:5777
Terminate
@ Terminate
Definition: TrainUnit.h:55
TTrack::ActiveTrackElementNameMap
TActiveTrackElementNameMap ActiveTrackElementNameMap
< map of coupled continuations
Definition: TrackUnit.h:801
TTrain::Crashed
bool Crashed
Definition: TrainUnit.h:514
TTrackElement::HLoc
int HLoc
Definition: TrackUnit.h:149
TTrain::HeadCodePosition
Graphics::TBitmap * HeadCodePosition[4]
Set from the HeadCodeGrPtr[4] pointer values, HeadCodePosition[0] is always the front,...
Definition: TrainUnit.h:530
TTrain::TrainID
int TrainID
the train's identification number
Definition: TrainUnit.h:384
Running
@ Running
Definition: TrainUnit.h:92
TAllFormattedTrains
std::vector< TTrainFormattedInformation > TAllFormattedTrains
vector of all timetabled trains for use in the formatted timetable
Definition: TrainUnit.h:296
TRailGraphics::ChangeBackgroundColour3
void ChangeBackgroundColour3(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour)
as above but uses Scanline
Definition: GraphicUnit.cpp:3800
TRailGraphics::smCyan
Graphics::TBitmap * smCyan
Definition: GraphicUnit.h:899
TTrain::LongServRefTextV
int LongServRefTextV
stores the VPos position of the service ref train name
Definition: TrainUnit.h:502
TTrainController::MovingSuccessor
bool MovingSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15800
TTrainController::LogActionError
void LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
Send an error message to the performance log and file, and as a warning if appropriate.
Definition: TrainUnit.cpp:17361
TTrainController::TrainVectorAt
TTrain & TrainVectorAt(int Caller, int VecPos)
Return a reference to the train at position VecPos in the TrainVector, carries out range checking on ...
Definition: TrainUnit.cpp:18147
TTrain::FrontElementSpeedLimit
int FrontElementSpeedLimit
Definition: TrainUnit.h:478
TTrack::NumberOfPlatforms
int NumberOfPlatforms(int Caller, AnsiString LocationName)
Returns the number of separate platforms (not platform elements) at a given location,...
Definition: TrackUnit.cpp:12330
TTrack::TSigElement::SpeedTag
int SpeedTag
the TrackElement SpeedTag value - specifies the signal element
Definition: TrackUnit.h:728
TTrain::DeleteTrain
void DeleteTrain(int Caller)
This is a housekeeping function to delete train heap objects (bitmaps) explicitly rather than by usin...
Definition: TrainUnit.cpp:264
TTrainController::NumFailures
int NumFailures
Definition: TrainUnit.h:905
TTrain::ExitSpeedFull
double ExitSpeedFull
speed when leaving the next element
Definition: TrainUnit.h:442
TTrain::SetHeadCodeGraphics
void SetHeadCodeGraphics(int Caller, AnsiString Code)
Set the four HeadCodeGrPtr[4] pointers to the appropriate character graphics with the current backgro...
Definition: TrainUnit.cpp:2990
TAllRoutes::SetTrailingSignalsOnAutoSigsRoute
void SetTrailingSignalsOnAutoSigsRoute(int Caller, int TrackVectorPosition, int XLinkPos)
Enter with signal at TrackVectorElement already set to red by the passing train.
Definition: TrackUnit.cpp:20843
clB5G5R5
#define clB5G5R5
Definition: GraphicUnit.h:287
TUtilities::TimeStamp
AnsiString TimeStamp()
creates a string of the form 'hh:mm:ss' for use in call & event logging
Definition: Utilities.cpp:73
TTrainController::RandomFailureCounter
unsigned int RandomFailureCounter
new at v2.4.0 for train failures, resets after 53 seconds (53 prime so can trigger at any clock time)
Definition: TrainUnit.h:921
TAllRoutes::TLockedRouteClass::RouteNumber
int RouteNumber
the vector position number of the relevant route in AllRoutesVector
Definition: TrackUnit.h:1661
TTrainController::AddTrain
bool AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
Introduce a new train to the railway, with the characteristics specified, returns true for success,...
Definition: TrainUnit.cpp:10746
TTrainController::~TTrainController
~TTrainController()
Destructor.
Definition: TrainUnit.cpp:10315
TTrain::RepeatShuttleOrNewNonRepeatService
void RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7229
TTrainController::TwoLocationList
TServiceCallingLocsList TwoLocationList
Definition: TrainUnit.h:915
TTrain::GetNewServiceDepartureInfo
AnsiString GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
called during FloatingLabelNextString to find the next service departure time & next location (last b...
Definition: TrainUnit.cpp:8040
TTrain::Description
AnsiString Description
needs own HeadCode & description because repeat entries will differ from TrainDataEntry values (Descr...
Definition: TrainUnit.h:338
TUtilities::CheckAndReadFileInt
bool CheckAndReadFileInt(std::ifstream &InFile, int Lowest, int Highest, int &OutInt)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success ...
Definition: Utilities.cpp:304
TRailGraphics::Code7
Graphics::TBitmap * Code7
Definition: GraphicUnit.h:997
TTrackElement::ActiveTrackElementName
AnsiString ActiveTrackElementName
Location name used either in the timetable or for a continuation (continuation names not used in time...
Definition: TrackUnit.h:128
TRailGraphics::bm94set
Graphics::TBitmap * bm94set
Definition: GraphicUnit.h:520
TUtilities::DelayMode
TDelayMode DelayMode
specifies whether no delays or minor, moderate or major random delays are to be applied (added at v2....
Definition: Utilities.h:123
TTrainController::LogEvent
void LogEvent(AnsiString Str)
store Str to the event log - moved from TUtilities for v0.6 so can record the tt clock value
Definition: TrainUnit.cpp:10327
TRailGraphics::CodeO
Graphics::TBitmap * CodeO
Definition: GraphicUnit.h:1014
TTrain::ExitPair
THVShortPair ExitPair
H & V coordinates of the exit element related to TimeToExit, new for multiplayer.
Definition: TrainUnit.h:493
TAllRoutes::SignallerRemovedTrainAutoRoute
TOneRoute SignallerRemovedTrainAutoRoute
if train was on an AutoSigsRoute when removed then this stores the route so that signals can be reset
Definition: TrackUnit.h:1748
TTrainController::MassHigh
bool MassHigh
Definition: TrainUnit.h:861
TTrainController::CheckAndPopulateListOfIDs
bool CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
Used to compile ExitList from a string list of element IDs, returns true for success or gives a messa...
Definition: TrainUnit.cpp:13144
TTrain::PlotTrainWithNewBackgroundColour
void PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
Changes the train's background colour (e.g. to pale green if stopped at a station) Note that this use...
Definition: TrainUnit.cpp:4058
TUtilities::SaveFileString
void SaveFileString(std::ofstream &OutFile, AnsiString SaveString)
stores the string value to the file, then a '0' delimiter then a CR
Definition: Utilities.cpp:159
TTrain::PlotAlternativeTrackRouteGraphic
void PlotAlternativeTrackRouteGraphic(int Caller, unsigned int LagElement, int LagELinkPos, int HOffset, int VOffset, TStraddle StraddleValue)
When a train moves off a bridge the other track may contain a route or have a train on it that has be...
Definition: TrainUnit.cpp:3758
TTrain::ExitTimeFull
TDateTime ExitTimeFull
times used in SetTrainMovementValues corresponding to the next element the train runs on
Definition: TrainUnit.h:486
TTrainController::CalcDistanceToRedSignalandStopTime
int CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos, bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime, float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
new v2.2.0 (DistanceToExit added for multiplayer), calcs distances to red signal & exit,...
Definition: TrainUnit.cpp:22789
TTrack::GapFlashRed
TGraphicElement * GapFlashRed
the red & green circle graphics used to show where the gaps are
Definition: TrackUnit.h:809
TTrainController::ProcessOneTimetableEntry
bool ProcessOneTimetableEntry(int Caller, int Count, AnsiString OneEntry, bool &EndOfFile, bool FinalCall, bool GiveMessages, bool CheckLocationsExistInRailway)
Carry out preliminary (mainly syntax) validity checks on a single timetable service entry and (if Fin...
Definition: TrainUnit.cpp:11687
LocTypeForRepeatEntry
@ LocTypeForRepeatEntry
Definition: TrainUnit.h:76
SignallerChangeDirection
@ SignallerChangeDirection
Definition: TrainUnit.h:57
TAllRoutes::TLockedRouteClass
Handles routes that are locked because of approaching trains.
Definition: TrackUnit.h:1659
TTrain::TrainAtLocation
bool TrainAtLocation(int Caller, AnsiString &LocationName)
True when the train is stopped at a timetabled location.
Definition: TrainUnit.cpp:9683
TTrain::IsTrainIDOnBridgeTrackPos01
bool IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 0 & 1.
Definition: TrainUnit.cpp:3622
TActionVectorEntry
Contains a single train action in a timetable - repeat entry is also of this class though no train ac...
Definition: TrainUnit.h:123
TTrainController::TotLateExitMins
float TotLateExitMins
Definition: TrainUnit.h:881
TTrainController::CheckSessionContinuationAutoSigEntries
bool CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for ContinuationAutoSigEntries, true for success.
Definition: TrainUnit.cpp:17993
TOneCompleteFormattedTrain::OneFormattedTrainVector
TOneFormattedTrainVector OneFormattedTrainVector
Definition: TrainUnit.h:277
TRailGraphics::Code1
Graphics::TBitmap * Code1
Definition: GraphicUnit.h:991
TTrainController::TimetableMessage
void TimetableMessage(bool GiveMessages, AnsiString Message)
Sends a message to the user if GiveMessages is true, including ServiceReference (see above) if not nu...
Definition: TrainUnit.cpp:17302
TDisplay
Definition: DisplayUnit.h:50
TTrackElement::ConnLinkPos
int ConnLinkPos[4]
Connecting element link position (i.e. array positions of the connecting element links,...
Definition: TrackUnit.h:147
TTrainController::ContinuationAutoSigVector
TContinuationAutoSigVector ContinuationAutoSigVector
vector for TContinuationAutoSigEntry objects
Definition: TrainUnit.h:923
TTrain::StoppedWithoutPower
bool StoppedWithoutPower
Definition: TrainUnit.h:515
TTrain::NameInTimetableBeforeCDT
int NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
Returns the number by which the train ActionVectorEntryPtr needs to be incremented to point to the lo...
Definition: TrainUnit.cpp:5207
TimeCmdDescription
@ TimeCmdDescription
Definition: TrainUnit.h:71
TTrainFormattedInformation::Header
AnsiString Header
description, mass, power, brake rate etc
Definition: TrainUnit.h:289
TActionEventType
TActionEventType
Used for reporting error conditions & warnings.
Definition: TrainUnit.h:40
TTrainController::TrainExistsAtIdent
bool TrainExistsAtIdent(int Caller, int TrainID)
new at v2.4.0 return true if find the train (added at v2.4.0 as can select a removed train in Actions...
Definition: TrainUnit.cpp:11087
SignallerJoin
@ SignallerJoin
Definition: TrainUnit.h:56
TTrain::RearStartElement
int RearStartElement
start TrackVectorPosition element for rear of train
Definition: TrainUnit.h:372
TTrain::StepForwardFlag
bool StepForwardFlag
set when the signaller command to step forward one element has been given
Definition: TrainUnit.h:424
TTrainController::SplitTrainInfo
bool SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed, int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
Parse a train information entry, return true for success; PowerAtRail changed to double& from int& at...
Definition: TrainUnit.cpp:13244
TTrainController::TotEarlyExitMins
float TotEarlyExitMins
Definition: TrainUnit.h:877
TFixedTrackPiece::Config
TConfiguration Config[4]
the type of link - see TConfiguration above
Definition: TrackUnit.h:97
TTrainController::Operate
void Operate(int Caller)
called every clock tick to introduce new trains and update existing trains
Definition: TrainUnit.cpp:10341
TTrackElement::Failed
bool Failed
New parameter added at v2.13.0 for failed points, signals & TSRs.
Definition: TrackUnit.h:141
TTrackElement::TrainIDOnElement
int TrainIDOnElement
Definition: TrackUnit.h:155
TTrainController::SkippedTTEvents
int SkippedTTEvents
Definition: TrainUnit.h:900
TRailGraphics::smRed
Graphics::TBitmap * smRed
Definition: GraphicUnit.h:906
TTrain::PowerAtRail
double PowerAtRail
< saves MinDwellTime from the arrival event so it can be retrieved at departure
Definition: TrainUnit.h:466
TRailGraphics::LCPlainMan
Graphics::TBitmap * LCPlainMan
Definition: GraphicUnit.h:750
TDisplay::GetOutputLog4
TLabel * GetOutputLog4()
Definition: DisplayUnit.h:160
TAllRoutes::RebuildRailwayFlag
bool RebuildRailwayFlag
this is set whenever a route has to be cancelled forcibly in order to force a ClearandRebuildRailway ...
Definition: TrackUnit.h:1729
TTrainDataEntry::MaxRunningSpeed
double MaxRunningSpeed
in km/h
Definition: TrainUnit.h:224
TUtilities::LoadFileInt
int LoadFileInt(std::ifstream &InFile)
loads an int value from the file
Definition: Utilities.cpp:186
TTrackElement::ElementID
AnsiString ElementID
the element identifier based on position in the railway
Definition: TrackUnit.h:130
TUtilities::MinorDelayCutoff
float MinorDelayCutoff
Definition: Utilities.h:50
TTimeToExitMultiMapEntry
std::pair< THVShortPair, TExitInfo > TTimeToExitMultiMapEntry
Definition: TrainUnit.h:117
TRailGraphics::Code6
Graphics::TBitmap * Code6
Definition: GraphicUnit.h:996
TTrainDataEntry::TrainOperatingDataVector
TTrainOperatingDataVector TrainOperatingDataVector
operating information for the train including all its repeats
Definition: TrainUnit.h:238
TTrain::DelayedRandMins
double DelayedRandMins
the remaining random delay at any point in time for the train (added at v2.13.0)
Definition: TrainUnit.h:458
TTrackElement::VLoc
int VLoc
The h & v locations in the railway (top lh corner of the first build screen = 0,0)
Definition: TrackUnit.h:149
TTrainController::OperatingTrainLateMins
float OperatingTrainLateMins
total late minutes of operating trains on exit operation for locations not reached yet
Definition: TrainUnit.h:870
TTrack::GetNonPointsOppositeLinkPos
int GetNonPointsOppositeLinkPos(int LinkPosIn)
Return the corresponding link position (track always occupies either links 0 & 1 or 2 & 3)
Definition: TrackUnit.h:911
TTrain::UnplotTrain
void UnplotTrain(int Caller)
Unplot train from screen in zoomed-in mode.
Definition: TrainUnit.cpp:591
TTrain::IsThereAnAdjacentTrain
bool IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
Definition: TrainUnit.cpp:5693
TTrain::TRSTime
TDateTime TRSTime
Definition: TrainUnit.h:488
clB1G0R0
#define clB1G0R0
Definition: GraphicUnit.h:79
TAllRoutes::TLockedRouteClass::LastTrackVectorPosition
unsigned int LastTrackVectorPosition
the TrackVector position of the last (i.e. most forward) element in the route (this will be truncated...
Definition: TrackUnit.h:1665
TActionVectorEntry::OneLineText
AnsiString OneLineText
Definition: TrainUnit.h:125
FailMissedJBO
@ FailMissedJBO
Definition: TrainUnit.h:42
TUtilities::EventLog
std::deque< AnsiString > EventLog
event store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:113
TAllRoutes::NoRoute
@ NoRoute
Definition: TrackUnit.h:1675
TActionVectorEntry::FormatType
TTimetableFormatType FormatType
defines the timetable action type
Definition: TrainUnit.h:144
TTrain::RemainHereLogNotSent
bool RemainHereLogNotSent
flag to prevent repeated logs, new at v1.2.0
Definition: TrainUnit.h:350
TRailGraphics::CodeU
Graphics::TBitmap * CodeU
Definition: GraphicUnit.h:1020
TTrainController::RestartTime
TDateTime RestartTime
TTClockTime when operation pauses ( = timetable start time prior to operation) TTClockTime is calcula...
Definition: TrainUnit.h:760
Moderate
@ Moderate
Definition: Utilities.h:38
TTrainController::TimetableStartTime
TDateTime TimetableStartTime
the start time of the current timetable
Definition: TrainUnit.h:758
clSPADBackground
#define clSPADBackground
Definition: GraphicUnit.h:301
TTrainController::UnplotTrains
void UnplotTrains(int Caller)
unplot all trains from screen
Definition: TrainUnit.cpp:10730
TRailGraphics::smBrightGreen
Graphics::TBitmap * smBrightGreen
Definition: GraphicUnit.h:897
TTrainController::EarlyArrivals
int EarlyArrivals
Definition: TrainUnit.h:886
FailCreateTrain
@ FailCreateTrain
Definition: TrainUnit.h:41
TTrain::IncrementalDigits
int IncrementalDigits
the number of digits to increment by in repeat entries
Definition: TrainUnit.h:368
TTrainController::CreateFormattedTimetable
void CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
Examines the internal timetable (TrainDataVector) and creates from it a chronological (....
Definition: TrainUnit.cpp:18160
TTrack::RouteFlashFlag
bool RouteFlashFlag
true while a route is flashing prior to being set
Definition: TrackUnit.h:773
TUtilities::ShowLongServRefsFlag
bool ShowLongServRefsFlag
when set long service references show on screen, initialised in Interface constructor
Definition: Utilities.h:83
TimeCmdMaxSpeed
@ TimeCmdMaxSpeed
Definition: TrainUnit.h:71
TRailGraphics::Code_v
Graphics::TBitmap * Code_v
Definition: GraphicUnit.h:985
TTrackElement::StationEntryStopLinkPos1
int StationEntryStopLinkPos1
Definition: TrackUnit.h:153
Major
@ Major
Definition: Utilities.h:38
Points
@ Points
Definition: TrackUnit.h:66
TTrainController::SplitEntry
bool SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second, AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartPosition, TTimetableFormatType &TimetableFormatType, TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
Parse a single timetable service action, return true for success.
Definition: TrainUnit.cpp:12533
TRailGraphics::LCPlain
Graphics::TBitmap * LCPlain
Definition: GraphicUnit.h:743
TTrain::LastActionDelayFlag
bool LastActionDelayFlag
used when trains join to ensure that there is a 30 second delay before the actual join takes place af...
Definition: TrainUnit.h:408
TTrainController::TotLatePassMins
float TotLatePassMins
Definition: TrainUnit.h:880
TTrainController::StripSpaces
void StripSpaces(int Caller, AnsiString &Input)
Strip both leading and trailing spaces at ends of Input and spaces before and after all commas and se...
Definition: TrainUnit.cpp:16402
TTrain::RemoveLongServRef
void RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp)
Removes the displayed train service ref.
Definition: TrainUnit.cpp:2742
FailMissedJoinOther
@ FailMissedJoinOther
Definition: TrainUnit.h:42
TTrain::JoinedOtherTrainFlag
bool JoinedOtherTrainFlag
true when the train has joined another train following an 'Fjo' timetable command or a signaller join...
Definition: TrainUnit.h:406
TTrainController::OpActionPanelHintDelayCounter
unsigned int OpActionPanelHintDelayCounter
new v2.2.0 on start operation delays the op action panel headcode display for about 3 secs while hint...
Definition: TrainUnit.h:919
TTrainController::BFLow
bool BFLow
Definition: TrainUnit.h:861
TTrainController::TotArrDepPass
int TotArrDepPass
Definition: TrainUnit.h:903
SignallerPassRedSignal
@ SignallerPassRedSignal
Definition: TrainUnit.h:57
TRailGraphics::ChangeForegroundColour
void ChangeForegroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
Definition: GraphicUnit.cpp:3648
TRailGraphics::CodeE
Graphics::TBitmap * CodeE
Definition: GraphicUnit.h:1004
FailLockedRoute
@ FailLockedRoute
Definition: TrainUnit.h:41
TTrain::SignallerRemoved
bool SignallerRemoved
set when removed under signaller control to force a removal from the display at the next clock tick
Definition: TrainUnit.h:418
clB5G3R0
#define clB5G3R0
Definition: GraphicUnit.h:268
TTrain::FrontCodePtr
Graphics::TBitmap * FrontCodePtr
points to the front headcode segment, this is set to red or blue depending on TrainMode
Definition: TrainUnit.h:534
TOneRoute::SetRouteSignals
void SetRouteSignals(int Caller) const
Called when setting a route to set all signals appropriately. Also called when a new train is added a...
Definition: TrackUnit.cpp:18334
Continuation
@ Continuation
Definition: TrackUnit.h:66
TTrainController::CallOnWarning
bool CallOnWarning
Definition: TrainUnit.h:847
TTrain::ActualArrivalTime
TDateTime ActualArrivalTime
location departure time and 'train ready to start' time (TRSTime is 10 seconds before the ReleaseTime...
Definition: TrainUnit.h:488
TTrack::GetFilletGraphic
Graphics::TBitmap * GetFilletGraphic(int Caller, TTrackElement TrackElement)
Return a pointer to the point fillet (the bit that appears to move when points are changed) for the p...
Definition: TrackUnit.cpp:7858
GraphicUnit.h
PerfLogUnit.h
TextUnit.h
TUtilities::MajorDelayFactor
float MajorDelayFactor
Definition: Utilities.h:55
TTrack::PointFlashFlag
bool PointFlashFlag
< true if a route set through an LC that is closed to trains (& therefore needs to be opened)
Definition: TrackUnit.h:771
TTrack::ThisLocationLongEnoughForSplit
bool ThisLocationLongEnoughForSplit(int Caller, AnsiString HeadCode, int TrainID, AnsiString LocationName, int LeadElement, int LeadExitPos, int MidElement, int MidEntryPos, int &FrontTrainFrontPos, int &FrontTrainRearPos, int &RearTrainFrontPos, int &RearTrainRearPos, bool &TemporaryDelay)
checks if the track that the train is on is long enough for a split, returns false if not,...
Definition: TrackUnit.cpp:11556
TTrainController::OnTimeDeps
int OnTimeDeps
Definition: TrainUnit.h:896
TTrain::RestoreTimetableLocation
AnsiString RestoreTimetableLocation
stores the location name at which signaller control is taken, to ensure that it is back at that locat...
Definition: TrainUnit.h:506
TTrainController::TimetableIntegrityCheck
bool TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway)
Checks overall timetable integrity, calls many other specific checking functions, returns true for su...
Definition: TrainUnit.cpp:11559
AllRoutes
TAllRoutes * AllRoutes
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:54
TTrain::SignallerMaxSpeed
int SignallerMaxSpeed
maximum train speed under signaller control (in km/h)
Definition: TrainUnit.h:378
NoEvent
@ NoEvent
Definition: TrainUnit.h:41
FailSplitDueToOtherTrain
@ FailSplitDueToOtherTrain
Definition: TrainUnit.h:41
TTrainController::SendPerformanceSummary
void SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
At the end of operation a summary of overall performance is sent to the performance file by this func...
Definition: TrainUnit.cpp:21980
TTrack::TInfrastructureFailureEntry::FailureTime
TDateTime FailureTime
Definition: TrackUnit.h:716
TTrain::ResetTrainElementID
void ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
After a train has moved off an element that element has its TrainIDOnElement value set back to -1 to ...
Definition: TrainUnit.cpp:3716
TRailGraphics::smMagenta
Graphics::TBitmap * smMagenta
Definition: GraphicUnit.h:902
RepairFailedTrain
@ RepairFailedTrain
Definition: TrainUnit.h:57
TTrainController::AllServiceCallingLocsMap
TAllServiceCallingLocsMap AllServiceCallingLocsMap
Definition: TrainUnit.h:828
TRailGraphics::smPaleGreen
Graphics::TBitmap * smPaleGreen
Definition: GraphicUnit.h:905
TRailGraphics::CodeS
Graphics::TBitmap * CodeS
Definition: GraphicUnit.h:1018
TTrainController::ContinuationEntryFloatingTTString
AnsiString ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits)
Build string for use in floating window for expected trains at continuations.
Definition: TrainUnit.cpp:11118
TFixedTrackPiece::SpeedTag
int SpeedTag
The element identification number - corresponds to the relevant SpeedButton->Tag.
Definition: TrackUnit.h:88
TTrain::FollowOnServiceRef
AnsiString FollowOnServiceRef
Definition: TrainUnit.h:340
TTrain::SPADFlag
bool SPADFlag
set when running past a red signal without permission flags to indicate relevant stop conditions or p...
Definition: TrainUnit.h:512
TTrain::Stopped
bool Stopped()
True if the train has stopped for any reason.
Definition: TrainUnit.h:718
Minor
@ Minor
Definition: Utilities.h:38
TTrack::SigTableGroundSignal
TSigElement SigTableGroundSignal[40]
new at version 0.6 for ground signals
Definition: TrackUnit.h:742
TTrain::TrainToJoinIsAdjacent
bool TrainToJoinIsAdjacent(int Caller, TTrain *&TrainToJoin)
True for a train waiting to join another when the other train is adjacent.
Definition: TrainUnit.cpp:7044
TActionVectorEntry::ExitList
TNumList ExitList
the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
Definition: TrainUnit.h:142
TTrainController::OnTimeExits
int OnTimeExits
Definition: TrainUnit.h:898
TTrain::OldZoomOutElement
int OldZoomOutElement[3]
stores the Lead, Mid & Lag TrackVectorPositions, used for unplotting trains from the old position in ...
Definition: TrainUnit.h:521
TPrefDirElement::GetELink
int GetELink() const
Returns ELink.
Definition: TrackUnit.h:269
TDisplay::GetOutputLog8
TLabel * GetOutputLog8()
Definition: DisplayUnit.h:180
WaitingForJBO
@ WaitingForJBO
Definition: TrainUnit.h:44
TUtilities::CheckFileDouble
bool CheckFileDouble(std::ifstream &InFile)
checks that the value is a double, returns true for success
Definition: Utilities.cpp:355
TTrainDataEntry::ExplicitDescription
bool ExplicitDescription
< headcode is the first train's headcode, rest are calculated from repeat information; ServiceReferen...
Definition: TrainUnit.h:220
TTrain::Straddle
TStraddle Straddle
the current Straddle value of the train (see TStraddle above)
Definition: TrainUnit.h:547
SignallerStop
@ SignallerStop
Definition: TrainUnit.h:57
TRailGraphics::CodeB
Graphics::TBitmap * CodeB
Definition: GraphicUnit.h:1001
TAllRoutes::DiagonalFouledByRouteOrTrain
bool DiagonalFouledByRouteOrTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
The track geometry allows diagonals to cross without occupying the same track element,...
Definition: TrackUnit.cpp:21521
TTrain::RevisedStoppedAtLoc
bool RevisedStoppedAtLoc() const
Definition: TrainUnit.h:555
Display
TDisplay * Display
The object pointer for the on-screen display, object created in InterfaceUnit.
Definition: DisplayUnit.cpp:54
TTrack::GetVLocMin
int GetVLocMin()
Definition: TrackUnit.h:905
TRailGraphics::Code3
Graphics::TBitmap * Code3
Definition: GraphicUnit.h:993
TUtilities::CheckFileBool
bool CheckFileBool(std::ifstream &InFile)
checks that the value is a bool returns true for success
Definition: Utilities.cpp:233
TTrain::MaximumPowerLimit
static const int MaximumPowerLimit
Watts (i.e. 100MW)
Definition: TrainUnit.h:326
TTrain::PlotStartPosition
void PlotStartPosition(int Caller)
Plots the train and sets up all relevant members for a new train when it is introduced into the railw...
Definition: TrainUnit.cpp:316
TTrack::TActiveTrackElementNameMapEntry
std::pair< AnsiString, int > TActiveTrackElementNameMapEntry
Definition: TrackUnit.h:710
TTrainController::SingleServiceOutput
void SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
Outputs the single service vector for train direction analysis purposes in timetable conflict analysi...
Definition: TrainUnit.cpp:21121
TTrack::TInfrastructureFailureEntry::RepairTime
TDateTime RepairTime
Definition: TrackUnit.h:717
TTrainController::TContinuationAutoSigEntry::SecondDelay
double SecondDelay
Definition: TrainUnit.h:769
TTrain::UnplotTrainInZoomOutMode
void UnplotTrainInZoomOutMode(int Caller)
Unplot train from screen in zoomed-out mode.
Definition: TrainUnit.cpp:9646
TTimetableFormatType
TTimetableFormatType
Timetable entry types.
Definition: TrainUnit.h:69
TTrainController::TrainFailedWarning
bool TrainFailedWarning
Flags to enable the relevant warning graphics to flash at the left hand side of the screen.
Definition: TrainUnit.h:847
TTrain::NextTrainID
static int NextTrainID
< km/h
Definition: TrainUnit.h:335
TTrainController::TLocServiceTimes
Class used for timetable conflict file compilation.
Definition: TrainUnit.h:814
TTrainOperatingData::RunningEntry
TRunningEntry RunningEntry
Definition: TrainUnit.h:196
NotAShuttleLink
@ NotAShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::gl91set
Graphics::TBitmap * gl91set
Definition: GraphicUnit.h:726
TRailGraphics::Code_y
Graphics::TBitmap * Code_y
Definition: GraphicUnit.h:988
TAllRoutes::CheckMapAndRoutes
void CheckMapAndRoutes(int Caller)
Diagnostic function - checks equivalence for each route between entries in PrefDirVector and those in...
Definition: TrackUnit.cpp:20617
TAllRoutes::GetRouteElementDataFromRoute2MultiMap
TRouteElementPair GetRouteElementDataFromRoute2MultiMap(int Caller, int HLoc, int VLoc, TRouteElementPair &SecondPair)
Retrieve up to two TRouteElementPair entries from Route2MultiMap at H & V, the first as a function re...
Definition: TrackUnit.cpp:20573
TActionVectorEntry::MinDwellTime
double MinDwellTime
Definition: TrainUnit.h:134
TTrain::CumulativeDelayedRandMinsOneTrain
double CumulativeDelayedRandMinsOneTrain
the running total of all random delays including knock-on delays for a single train,...
Definition: TrainUnit.h:462
TTrainController::TrainAdded
bool TrainAdded
true when a train has been added by a split (occurs outside the normal train introduction process)
Definition: TrainUnit.h:851
TActionVectorEntry::LocationName
AnsiString LocationName
Definition: TrainUnit.h:125
TTrainFormattedInformation::OneCompleteFormattedTrainVector
TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector
Definition: TrainUnit.h:293
ShuttleFinishedRemainingHere
@ ShuttleFinishedRemainingHere
Definition: TrainUnit.h:44
FailUnexpectedBuffers
@ FailUnexpectedBuffers
Definition: TrainUnit.h:42
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
Definition: TrackUnit.h:155
TRailGraphics::TempBackground
Graphics::TBitmap * TempBackground
Definition: GraphicUnit.h:909
TTrackElement::StationEntryStopLinkPos4
int StationEntryStopLinkPos4
Used for track at platforms ( 1 & 2) and non-station named locations (1 - 4) to mark the train front ...
Definition: TrackUnit.h:153
TTrain::TimeTimeLocArrived
bool TimeTimeLocArrived
indicates whether has arrived (true) or not when ActionVectorEntryPtr->FormatType == TimeTimeLoc
Definition: TrainUnit.h:348
TActionType
TActionType
Used in LogAction when reporting a train action to the performance log & file.
Definition: TrainUnit.h:54
TTrainController::TContinuationAutoSigEntry::ThirdDelay
double ThirdDelay
Delays in seconds before consecutive signal changes - these correspond to the times taken for trains ...
Definition: TrainUnit.h:769
TTrain::LeadEntryPos
int LeadEntryPos
Definition: TrainUnit.h:382
TTrainController::LateArrivals
int LateArrivals
Definition: TrainUnit.h:890
TRailGraphics::CodeZ
Graphics::TBitmap * CodeZ
Definition: GraphicUnit.h:1025
FailMissedCMS
@ FailMissedCMS
Definition: TrainUnit.h:45
NoShuttleLink
@ NoShuttleLink
Definition: TrainUnit.h:86
TTrainController::ReplotTrains
void ReplotTrains(int Caller, TDisplay *Disp)
plot all trains on the display
Definition: TrainUnit.cpp:10697
TTrainController::CheckShuttleServiceIntegrity
bool CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
Check that each shuttle service ends either in Fns or Fxx-sh (though a single service can't end in Fx...
Definition: TrainUnit.cpp:17249
TTrain::FloatingTimetableString
AnsiString FloatingTimetableString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the timetable.
Definition: TrainUnit.cpp:8256
TAllRoutes::GetModifiableRouteAt
TOneRoute & GetModifiableRouteAt(int Caller, int At)
Returns a modifiable reference to the route at AllRoutesVector position 'At', after performing range ...
Definition: TrackUnit.cpp:19846
TTrain::NewShuttleFromNonRepeatService
void NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed when a new shuttle service is created from a non-repeating (F-nshs) serv...
Definition: TrainUnit.cpp:7101
TTrain::AValue
double AValue
< only true when a train has become a follow-on service early and the follow-on service normally pass...
Definition: TrainUnit.h:434
FailUnexpectedExitRailway
@ FailUnexpectedExitRailway
Definition: TrainUnit.h:42
TTrain::FrontTrainSplit
void FrontTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the front.
Definition: TrainUnit.cpp:6196
TTrainController::WithinTimeRange
bool WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange)
check whether the two times are within the range in minutes specified and return true if so....
Definition: TrainUnit.cpp:21265
TTrainController::WriteTrainsToImage
void WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TInterface::SaveOperatingImage1Click) to write all trains to the image file.
Definition: TrainUnit.cpp:10715
TDisplay::DisplayOffsetH
static int DisplayOffsetH
the horizontal offset of the displayed screen (as viewpoint moves to the right [railway moves left] t...
Definition: DisplayUnit.h:77
TActionVectorEntry::LinkedTrainEntryPtr
TTrainDataEntry * LinkedTrainEntryPtr
link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle links
Definition: TrainUnit.h:152
TTrainController::AtLocSuccessor
bool AtLocSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15807
TTrain::ZeroPowerNoJoinedByMessage
bool ZeroPowerNoJoinedByMessage
Definition: TrainUnit.h:357
TTrainController::SaveSessionContinuationAutoSigEntries
void SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
save ContinuationAutoSigEntries to a session file
Definition: TrainUnit.cpp:17953
TTrainOperatingData::TrainID
int TrainID
Definition: TrainUnit.h:194
TUtilities::MaxRandomRepairTime
int MaxRandomRepairTime
Definition: Utilities.h:71
TDisplay::GetOutputLog3
TLabel * GetOutputLog3()
Definition: DisplayUnit.h:155
NoLocation
@ NoLocation
Definition: TrainUnit.h:76
TTrainController::CheckCrossReferencesAndSetData
bool CheckCrossReferencesAndSetData(int Caller, AnsiString SoughtHeadCode, AnsiString SeekingHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where all service cross references are checked for validity and set p...
Definition: TrainUnit.cpp:15928
TRailGraphics::bmTransparentBgnd
Graphics::TBitmap * bmTransparentBgnd
Definition: GraphicUnit.h:936
TTrainController::MinsToAnsiTime
AnsiString MinsToAnsiTime(int Input)
converts an integer minute value to string "HH:MM" added at v2.15.0
Definition: TrainUnit.cpp:17334
TTrain::FirstHalfMove
bool FirstHalfMove
true when the train is on the first half of an element when it displays as fully on two elements....
Definition: TrainUnit.h:404
FailLocTooShort
@ FailLocTooShort
Definition: TrainUnit.h:41
TTrainDataEntry::Mass
int Mass
in kg
Definition: TrainUnit.h:228
NoMode
@ NoMode
Definition: TrainUnit.h:64
TTrainController::LatePasses
int LatePasses
Definition: TrainUnit.h:892
TTrainController::SplitRepeat
bool SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &RepeatNumber, bool GiveMessages)
Parse a timetable repeat entry, return true for success.
Definition: TrainUnit.cpp:13588
TFixedTrackPiece::TrackType
TTrackType TrackType
the type of track element
Definition: TrackUnit.h:100
TTrainController::TLocServiceTimes::ArrTime
AnsiString ArrTime
Definition: TrainUnit.h:818
TTrainController::SSHigh
bool SSHigh
Definition: TrainUnit.h:861
TimeLoc
@ TimeLoc
Definition: TrainUnit.h:70
TActionVectorIterator
TActionVector::iterator TActionVectorIterator
iterator
Definition: TrainUnit.h:185
TPrefDirElement
Basic preferred direction or route element - track element with additional members.
Definition: TrackUnit.h:201
TTrainController::AvHoursIntValue
int AvHoursIntValue
Input in MTBFEditBox in timetable hours, min value is 1 and max is 10,000. Here because performance f...
Definition: TrainUnit.h:913
TTrainController::CrashedTrains
int CrashedTrains
Definition: TrainUnit.h:884
TTrainController::TTEditPanelVisible
bool TTEditPanelVisible
new at v2.6.0 so potential error message only shows in TTEdit mode
Definition: TrainUnit.h:859
TDisplay::WarningLog
void WarningLog(int Caller, AnsiString Statement)
Display warning message Statement in the bottom left hand warning position and scroll other messages ...
Definition: DisplayUnit.cpp:524
TTrain::StoppedAtSignal
bool StoppedAtSignal
Definition: TrainUnit.h:514
TTrainController::GetControllerTrainTime
TDateTime GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
Get the interval between repeats.
Definition: TrainUnit.cpp:11106
TRailGraphics::Code_u
Graphics::TBitmap * Code_u
Definition: GraphicUnit.h:984
TTrackElement::Attribute
int Attribute
special variable used only for points, signals & level crossings, ignored otherwise; points 0=set to ...
Definition: TrackUnit.h:143
TStraddle
TStraddle
Defines the train position with respect to the track elements; three consecutive elements are Lead (f...
Definition: TrainUnit.h:308
FailEntryRouteSetAgainst
@ FailEntryRouteSetAgainst
Definition: TrainUnit.h:45
TTrain::TrainSkippedEvents
int TrainSkippedEvents
stores the pointer increment from the current action in ActionVector for skipped actions when a depar...
Definition: TrainUnit.h:392
TTrainController::THCandTrainPosParam
std::pair< AnsiString, int > THCandTrainPosParam
Definition: TrainUnit.h:830
TExitInfo::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:109
TTrainDataVector
std::vector< TTrainDataEntry > TTrainDataVector
vector class for containing the whole timetable - one entry per timetable service entry (the object i...
Definition: TrainUnit.h:251
TTrainController::GetRepeatTime
TDateTime GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
Return the repeating service time.
Definition: TrainUnit.cpp:16922
clCrashedBackground
#define clCrashedBackground
Definition: GraphicUnit.h:294
TTrack::IsLCAtHV
bool IsLCAtHV(int Caller, int HLoc, int VLoc)
True if a level crossing is found at H & V.
Definition: TrackUnit.cpp:7597
TTrain::OpTimeToAct
float OpTimeToAct
in minutes: new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain,...
Definition: TrainUnit.h:472
TTrainController::TTClockTime
TDateTime TTClockTime
the time indicated by the timetable clock
Definition: TrainUnit.h:756
TAllRoutes::TLockedRouteClass::LockStartTime
TDateTime LockStartTime
the timetable time at which the route is locked, to start the 2 minute clock
Definition: TrackUnit.h:1669
TActionVectorEntry::SequenceType
TTimetableSequenceType SequenceType
indicates where in the sequence of codes the action lies
Definition: TrainUnit.h:148
TTrain::RearStartExitPos
int RearStartExitPos
the LinkPos value for the rear starting element (i.e. links to the front starting element)
Definition: TrainUnit.h:374
TTrainController::TLocServiceTimes::FrhMarker
AnsiString FrhMarker
Definition: TrainUnit.h:820
TRailGraphics::Code_o
Graphics::TBitmap * Code_o
Definition: GraphicUnit.h:978
TRailGraphics::CodeV
Graphics::TBitmap * CodeV
Definition: GraphicUnit.h:1021
TTrainController::TLocServiceTimes::ServiceAndRepeatNum
AnsiString ServiceAndRepeatNum
Definition: TrainUnit.h:816
TTrain::SetTrainElementID
void SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
When a train moves onto an element that element has its TrainIDOnElement value set to the TrainID val...
Definition: TrainUnit.cpp:3680
TActionVectorEntry::NonRepeatingShuttleLinkHeadCode
AnsiString NonRepeatingShuttleLinkHeadCode
Definition: TrainUnit.h:125
TActionVectorEntry::ShuttleLinkType
TTimetableShuttleLinkType ShuttleLinkType
indicates whether or not the action relates to a shuttle service link
Definition: TrainUnit.h:150
TTrain::TimetableFinished
bool TimetableFinished
set when there are no more timetable actions
Definition: TrainUnit.h:428
TUtilities::CallLog
std::deque< AnsiString > CallLog
call stack store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:111
TRailGraphics::CodeN
Graphics::TBitmap * CodeN
Definition: GraphicUnit.h:1013
TActionVectorEntry::Command
AnsiString Command
Definition: TrainUnit.h:125
TTrain::RepeatShuttleOrRemainHere
void RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7155
TTrain::CallingOnAllowed
bool CallingOnAllowed(int Caller)
True if the train can be called on at its current position - see detail in .cpp file.
Definition: TrainUnit.cpp:5402
TTrainController::TContinuationTrainExpectationEntry::HeadCode
AnsiString HeadCode
< service description
Definition: TrainUnit.h:789
TTrackElement::Length23
int Length23
Definition: TrackUnit.h:151
TTrain::SignallerStoppingFlag
bool SignallerStoppingFlag
set when the signaller stop command has been given
Definition: TrainUnit.h:420
TTrainController::NotStartedTrainLateArr
int NotStartedTrainLateArr
total number of arrivals & departures for trains that haven't started yet for locations not reached y...
Definition: TrainUnit.h:909
TUtilities::LoadFileBool
bool LoadFileBool(std::ifstream &InFile)
loads a bool value from the file
Definition: Utilities.cpp:169
TTrainController::SigSLow
bool SigSLow
Definition: TrainUnit.h:861
TRailGraphics::Code_m
Graphics::TBitmap * Code_m
Definition: GraphicUnit.h:976
TTrack::AnyLinkedBarrierDownVectorManual
bool AnyLinkedBarrierDownVectorManual(int Caller, int HLoc, int VLoc, int &BDVectorPos)
Checks BarrierDownVector and returns true if there is one that is linked to the LC at H & V positions...
Definition: TrackUnit.cpp:6621
TRailGraphics::gl92set
Graphics::TBitmap * gl92set
Definition: GraphicUnit.h:728
SNSShuttle
@ SNSShuttle
Definition: TrainUnit.h:70
RemoveTrain
@ RemoveTrain
Definition: TrainUnit.h:56
TTrainController::TLocServiceTimes::DepTime
AnsiString DepTime
Definition: TrainUnit.h:819
TTrainController::SetWarningFlags
void SetWarningFlags(int Caller)
This sets all the warning flags (CrashWarning, DerailWarning etc) to their required states after a se...
Definition: TrainUnit.cpp:22430
TDisplay::PlotSmallOutput
void PlotSmallOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot small (4x4) graphic PlotItem on the zoomed-out display at HPos & Vpos.
Definition: DisplayUnit.cpp:115
THVShortPair
std::pair< int, int > THVShortPair
Definition: InterfaceUnit.h:82
TTrainController::TContinuationTrainExpectationEntry
Class that stores data for trains expected at continuation entries (kept in a multimap - see below),...
Definition: TrainUnit.h:785
TTrainController::TContinuationTrainExpectationEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:787
SNSNonRepeatFromShuttle
@ SNSNonRepeatFromShuttle
Definition: TrainUnit.h:70
TTrainController::TotEarlyPassMins
float TotEarlyPassMins
Definition: TrainUnit.h:876
TRailGraphics::CodeH
Graphics::TBitmap * CodeH
Definition: GraphicUnit.h:1007
TTrainController::OpTimeToActMultiMap
TOpTimeToActMultiMap OpTimeToActMultiMap
added v2.2.0 for Op time to act display
Definition: TrainUnit.h:927
TTrainController::TContinuationAutoSigEntry::AccessNumber
int AccessNumber
the number of times the signal changing function has been accessed - starts at 0 and increments after...
Definition: TrainUnit.h:771
TActionVectorEntry::Reminder
unsigned int Reminder
Definition: TrainUnit.h:156
TActionVectorEntry::SignallerControl
bool SignallerControl
< string values for timetabled event entries, null on creation //NewMaxSpeed added at v2....
Definition: TrainUnit.h:130
TTrain::GetTrainTime
TDateTime GetTrainTime(int Caller, TDateTime Time)
Returns the timetable action time corresponding to 'Time' for this train, i.e. it adjusts the time va...
Definition: TrainUnit.cpp:5682
TTrain::BrakeRate
double BrakeRate
the current train brake rate
Definition: TrainUnit.h:452
TUtilities::LastDelayTTClockTime
double LastDelayTTClockTime
Clock time at which the latest delay for any train occurred. Used to prevent new delays within 5 minu...
Definition: Utilities.h:91
TTrackElement::SpeedLimit23
int SpeedLimit23
Element lengths and speed limits, ...01 is for the track with link positions [0] and [1],...
Definition: TrackUnit.h:151
TTrainController::TContinuationAutoSigEntry::PassoutTime
TDateTime PassoutTime
the timetable clock time at which the train exits from the continuation
Definition: TrainUnit.h:775
TTrain::FinishJoinLogSent
bool FinishJoinLogSent
Definition: TrainUnit.h:352
TTrack::GapFlashFlag
bool GapFlashFlag
true when a pair of connected gaps is flashing
Definition: TrackUnit.h:757
TRailGraphics::gl95set
Graphics::TBitmap * gl95set
Definition: GraphicUnit.h:732
TrainUnit.h
PassTime
@ PassTime
Definition: TrainUnit.h:71
TTrainController::IncorrectExits
int IncorrectExits
Definition: TrainUnit.h:889
TTrain::ContinuationExit
bool ContinuationExit(int Caller, int Element, int Exitpos) const
True if Element is a continuation and Exitpos is the continuation end.
Definition: TrainUnit.cpp:3604
TFixedTrackPiece::Link
int Link[4]
Track connection link values, max. of 4, unused = -1, top lh diag = 1, top = 2, top rh diag = 3,...
Definition: TrackUnit.h:90
TTrain::UpdateTrain
void UpdateTrain(int Caller)
Major function called at each clock tick for each train & handles all train movement & associated act...
Definition: TrainUnit.cpp:687
TRailGraphics::LockedRouteCancelPtr
Graphics::TBitmap * LockedRouteCancelPtr[10]
for locked route cancel graphic, 1 for each of 8 links, 0 & 5 included as for direction
Definition: GraphicUnit.h:1077
TRailGraphics::Code8
Graphics::TBitmap * Code8
Definition: GraphicUnit.h:998
TTrainController::SameDirection
bool SameDirection(int Caller, AnsiString Ref1, AnsiString Ref2, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1, TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
Determines whether two services are running in the same direction when they arrive or depart from Loc...
Definition: TrainUnit.cpp:21549
TTrain::TrainToBeJoinedByIsAdjacent
bool TrainToBeJoinedByIsAdjacent(int Caller, TTrain *&TrainToBeJoinedBy)
True for a train waiting to be joined when the joining train is adjacent.
Definition: TrainUnit.cpp:7072
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
Definition: TrackUnit.h:155
TTrain::SetTrainMovementValues
void SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
Calculates train speeds and times for the element that the train is about to enter....
Definition: TrainUnit.cpp:4100
TActionVectorEntry::Warning
bool Warning
if set triggers an alert in the warning and perf log panels when the action is reached
Definition: TrainUnit.h:132
TTrain::PickUpBackgroundBitmap
void PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
Store the background bitmap pointer (BackgroundPtr - see above) prior to being overwritten by the tra...
Definition: TrainUnit.cpp:3192
clStoppedTrainInFront
#define clStoppedTrainInFront
Definition: GraphicUnit.h:303
TDisplay::GetOutputLog10
TLabel * GetOutputLog10()
Definition: DisplayUnit.h:190
TTrainDataEntry::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:218
TTrain::SignallerChangeTrainDirection
void SignallerChangeTrainDirection(int Caller)
Unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd.
Definition: TrainUnit.cpp:7435
TTrain::ZeroPowerNoRearSplitMessage
bool ZeroPowerNoRearSplitMessage
Definition: TrainUnit.h:355
FailMissedChangeDirection
@ FailMissedChangeDirection
Definition: TrainUnit.h:43
NotSet
@ NotSet
Definition: TrackUnit.h:76
Repeat
@ Repeat
Definition: TrainUnit.h:71
TRailGraphics::Code_c
Graphics::TBitmap * Code_c
Definition: GraphicUnit.h:966
TTrainController::StripExcessFromHeadCode
void StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
change an extended headcode to an ordinary 4 character headcode
Definition: TrainUnit.cpp:15816
FinishSequence
@ FinishSequence
Definition: TrainUnit.h:81
TTrack::TIMPair
std::pair< unsigned int, unsigned int > TIMPair
TrackElement pair type used for inactive elements, values are vector positions.
Definition: TrackUnit.h:678
TUtilities::Clock2Stopped
bool Clock2Stopped
when true the main loop - Interface->ClockTimer2 - is stopped
Definition: Utilities.h:75
TTrack::OtherTrainOnTrack
bool OtherTrainOnTrack(int Caller, int TrackPos, int LinkPos, int OwnTrainID)
True if another train on LinkPos track of element at TrackPos, whether bridge or not,...
Definition: TrackUnit.cpp:11968
TTrackElement::GroundSignal
@ GroundSignal
Definition: TrackUnit.h:161
LevelCrossing
@ LevelCrossing
Definition: TrackUnit.h:67
TDisplay::GetOutputLog2
TLabel * GetOutputLog2()
Definition: DisplayUnit.h:150
TTrain::NotInService
bool NotInService
Definition: TrainUnit.h:515
TRailGraphics::CodeA
Graphics::TBitmap * CodeA
Definition: GraphicUnit.h:1000
TTrack::ActiveTrackElementNameMapCompiledFlag
bool ActiveTrackElementNameMapCompiledFlag
indicates that the ActiveTrackElementNameMap has been compiled
Definition: TrackUnit.h:751
TTrainController::LocServiceTimesLocationSort
bool LocServiceTimesLocationSort(TLocServiceTimes i, TLocServiceTimes j)
< Colour used to replace long serv. ref. text colour when removed - can't be transparent or text rema...
Definition: TrainUnit.h:947
TOneTrainFormattedEntry
A single train timetable action for use in a formatted timetable.
Definition: TrainUnit.h:259
TTrain::RearTrainSplit
void RearTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the rear.
Definition: TrainUnit.cpp:6360
TAllRoutes::TLockedRouteClass::RearTrackVectorPosition
unsigned int RearTrackVectorPosition
the TrackVector position of the rearmost element selected for truncation (this will be truncated)
Definition: TrackUnit.h:1663
TTrainController::ConsolidateSARNTArrDep
AnsiString ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival, bool &AnalysisError, int &MaxNumberOfSameDirections)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for arrivals (bool...
Definition: TrainUnit.cpp:21291
Track
TTrack * Track
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:53
TTrackElement::Conn
int Conn[4]
Connecting element position in TrackVector, set to -1 if no connecting link or if track not linked.
Definition: TrackUnit.h:145
Signaller
@ Signaller
Definition: TrainUnit.h:64
TTrain::TrainCrashedInto
int TrainCrashedInto
the TrainID of the train that this train has crashed into, recorded so that train can be marked and d...
Definition: TrainUnit.h:527
TRailGraphics::smYellow
Graphics::TBitmap * smYellow
Definition: GraphicUnit.h:907
RailGraphics
TRailGraphics * RailGraphics
the object pointer, object created in InterfaceUnit
Definition: GraphicUnit.cpp:50
TTrainOperatingData
Data for a specific train for use during operation.
Definition: TrainUnit.h:192
FailCreatePoints
@ FailCreatePoints
Definition: TrainUnit.h:41
TRailGraphics::smBlack
Graphics::TBitmap * smBlack
Definition: GraphicUnit.h:895
TTrain::TreatPassAsTimeLocDeparture
bool TreatPassAsTimeLocDeparture
< indicates failure
Definition: TrainUnit.h:432
Bridge
@ Bridge
Definition: TrackUnit.h:66
TRailGraphics::Code_p
Graphics::TBitmap * Code_p
Definition: GraphicUnit.h:979
TTrainFormattedInformation::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:291
TTrain::LagExitPos
int LagExitPos
TrackVector positions, & entry & exit connection positions for the elements that the train occupies.
Definition: TrainUnit.h:382
TTimetableSequenceType
TTimetableSequenceType
Definition: TrainUnit.h:80
TTrain::TimetableMaxRunningSpeed
double TimetableMaxRunningSpeed
the maximum train running speed when in timetable mode (see int SignallerMaxSpeed for signaller contr...
Definition: TrainUnit.h:444
SequTypeForRepeatEntry
@ SequTypeForRepeatEntry
Definition: TrainUnit.h:81
TRailGraphics::bmName
Graphics::TBitmap * bmName
Definition: GraphicUnit.h:528
clB0G0R0
#define clB0G0R0
Definition: GraphicUnit.h:37
Buffers
@ Buffers
Definition: TrackUnit.h:66
TActionVectorEntry::RearStartOrRepeatMins
int RearStartOrRepeatMins
Definition: TrainUnit.h:138
clFrontCodeTimetable
#define clFrontCodeTimetable
Definition: GraphicUnit.h:297
TTrack::OneNonStationLongEnoughForSplit
bool OneNonStationLongEnoughForSplit(int Caller, AnsiString LocationName)
As below but here allow points & crossovers.
Definition: TrackUnit.cpp:11392
TActionVectorEntry::OtherHeadCode
AnsiString OtherHeadCode
Definition: TrainUnit.h:125